Bug 1712669 - Make Windows tooltip margin depend on cursor size. r=yjuglaret,win...
[gecko.git] / widget / windows / nsWindow.cpp
blobb210381aa86ddfa599955604e514a5da5aaee570
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * nsWindow - Native window management and event handling.
10 * nsWindow is organized into a set of major blocks and
11 * block subsections. The layout is as follows:
13 * Includes
14 * Variables
15 * nsIWidget impl.
16 * nsIWidget methods and utilities
17 * nsSwitchToUIThread impl.
18 * nsSwitchToUIThread methods and utilities
19 * Moz events
20 * Event initialization
21 * Event dispatching
22 * Native events
23 * Wndproc(s)
24 * Event processing
25 * OnEvent event handlers
26 * IME management and accessibility
27 * Transparency
28 * Popup hook handling
29 * Misc. utilities
30 * Child window impl.
32 * Search for "BLOCK:" to find major blocks.
33 * Search for "SECTION:" to find specific sections.
35 * Blocks should be split out into separate files if they
36 * become unmanageable.
38 * Notable related sources:
40 * nsWindowDefs.h - Definitions, macros, structs, enums
41 * and general setup.
42 * nsWindowDbg.h/.cpp - Debug related code and directives.
43 * nsWindowGfx.h/.cpp - Graphics and painting.
47 /**************************************************************
48 **************************************************************
50 ** BLOCK: Includes
52 ** Include headers.
54 **************************************************************
55 **************************************************************/
57 #include "gfx2DGlue.h"
58 #include "gfxEnv.h"
59 #include "gfxPlatform.h"
61 #include "mozilla/AppShutdown.h"
62 #include "mozilla/AutoRestore.h"
63 #include "mozilla/Likely.h"
64 #include "mozilla/PreXULSkeletonUI.h"
65 #include "mozilla/Logging.h"
66 #include "mozilla/MathAlgorithms.h"
67 #include "mozilla/MiscEvents.h"
68 #include "mozilla/MouseEvents.h"
69 #include "mozilla/PresShell.h"
70 #include "mozilla/ScopeExit.h"
71 #include "mozilla/StaticPrefs_browser.h"
72 #include "mozilla/SwipeTracker.h"
73 #include "mozilla/TouchEvents.h"
74 #include "mozilla/TimeStamp.h"
76 #include "mozilla/ipc/MessageChannel.h"
77 #include <algorithm>
78 #include <limits>
80 #include "mozilla/widget/WinMessages.h"
81 #include "nsLookAndFeel.h"
82 #include "nsWindow.h"
83 #include "nsWindowTaskbarConcealer.h"
84 #include "nsAppRunner.h"
86 #include <shellapi.h>
87 #include <windows.h>
88 #include <wtsapi32.h>
89 #include <process.h>
90 #include <commctrl.h>
91 #include <dbt.h>
92 #include <unknwn.h>
93 #include <psapi.h>
94 #include <rpc.h>
95 #include <propvarutil.h>
96 #include <propkey.h>
98 #include "mozilla/Logging.h"
99 #include "prtime.h"
100 #include "prenv.h"
102 #include "mozilla/WidgetTraceEvent.h"
103 #include "nsContentUtils.h"
104 #include "nsISupportsPrimitives.h"
105 #include "nsITheme.h"
106 #include "nsIObserverService.h"
107 #include "nsIScreenManager.h"
108 #include "imgIContainer.h"
109 #include "nsIFile.h"
110 #include "nsIRollupListener.h"
111 #include "nsIClipboard.h"
112 #include "WinMouseScrollHandler.h"
113 #include "nsFontMetrics.h"
114 #include "nsIFontEnumerator.h"
115 #include "nsFont.h"
116 #include "nsRect.h"
117 #include "nsThreadUtils.h"
118 #include "nsNativeCharsetUtils.h"
119 #include "nsGkAtoms.h"
120 #include "nsCRT.h"
121 #include "nsAppDirectoryServiceDefs.h"
122 #include "nsWidgetsCID.h"
123 #include "nsTHashtable.h"
124 #include "nsHashKeys.h"
125 #include "nsString.h"
126 #include "mozilla/Components.h"
127 #include "nsNativeThemeWin.h"
128 #include "nsXULPopupManager.h"
129 #include "nsWindowsDllInterceptor.h"
130 #include "nsLayoutUtils.h"
131 #include "nsView.h"
132 #include "nsWindowGfx.h"
133 #include "gfxWindowsPlatform.h"
134 #include "gfxDWriteFonts.h"
135 #include "nsPrintfCString.h"
136 #include "mozilla/Preferences.h"
137 #include "SystemTimeConverter.h"
138 #include "WinTaskbar.h"
139 #include "WidgetUtils.h"
140 #include "WinWindowOcclusionTracker.h"
141 #include "nsIWidgetListener.h"
142 #include "mozilla/dom/Document.h"
143 #include "mozilla/dom/MouseEventBinding.h"
144 #include "mozilla/dom/Touch.h"
145 #include "mozilla/gfx/2D.h"
146 #include "mozilla/gfx/GPUProcessManager.h"
147 #include "mozilla/intl/LocaleService.h"
148 #include "mozilla/layers/WebRenderLayerManager.h"
149 #include "mozilla/WindowsVersion.h"
150 #include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
151 #include "mozilla/TextEventDispatcherListener.h"
152 #include "mozilla/widget/nsAutoRollup.h"
153 #include "mozilla/widget/PlatformWidgetTypes.h"
154 #include "mozilla/widget/Screen.h"
155 #include "nsStyleConsts.h"
156 #include "nsBidiKeyboard.h"
157 #include "nsStyleConsts.h"
158 #include "gfxConfig.h"
159 #include "InProcessWinCompositorWidget.h"
160 #include "InputDeviceUtils.h"
161 #include "ScreenHelperWin.h"
162 #include "mozilla/StaticPrefs_apz.h"
163 #include "mozilla/StaticPrefs_dom.h"
164 #include "mozilla/StaticPrefs_gfx.h"
165 #include "mozilla/StaticPrefs_layout.h"
166 #include "mozilla/StaticPrefs_ui.h"
167 #include "mozilla/StaticPrefs_widget.h"
168 #include "nsNativeAppSupportWin.h"
169 #include "mozilla/browser/NimbusFeatures.h"
171 #include "nsIGfxInfo.h"
172 #include "nsUXThemeConstants.h"
173 #include "KeyboardLayout.h"
174 #include "nsNativeDragTarget.h"
175 #include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
176 #include <zmouse.h>
177 #include <richedit.h>
179 #ifdef ACCESSIBILITY
180 # ifdef DEBUG
181 # include "mozilla/a11y/Logging.h"
182 # endif
183 # include "mozilla/a11y/Compatibility.h"
184 # include "oleidl.h"
185 # include <winuser.h>
186 # include "nsAccessibilityService.h"
187 # include "mozilla/a11y/DocAccessible.h"
188 # include "mozilla/a11y/LazyInstantiator.h"
189 # include "mozilla/a11y/Platform.h"
190 # if !defined(WINABLEAPI)
191 # include <winable.h>
192 # endif // !defined(WINABLEAPI)
193 #endif
195 #include "WindowsUIUtils.h"
197 #include "nsWindowDefs.h"
199 #include "nsCrashOnException.h"
201 #include "nsIContent.h"
203 #include "mozilla/BackgroundHangMonitor.h"
204 #include "WinIMEHandler.h"
206 #include "npapi.h"
208 #include <d3d11.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 #include "mozilla/gfx/DeviceManagerDx.h"
220 #include "mozilla/layers/APZInputBridge.h"
221 #include "mozilla/layers/InputAPZContext.h"
222 #include "mozilla/layers/KnowsCompositor.h"
223 #include "InputData.h"
225 #include "mozilla/TaskController.h"
226 #include "mozilla/Telemetry.h"
227 #include "mozilla/webrender/WebRenderAPI.h"
228 #include "mozilla/layers/IAPZCTreeManager.h"
230 #include "DirectManipulationOwner.h"
232 using namespace mozilla;
233 using namespace mozilla::dom;
234 using namespace mozilla::gfx;
235 using namespace mozilla::layers;
236 using namespace mozilla::widget;
237 using namespace mozilla::plugins;
239 /**************************************************************
240 **************************************************************
242 ** BLOCK: Variables
244 ** nsWindow Class static initializations and global variables.
246 **************************************************************
247 **************************************************************/
249 /**************************************************************
251 * SECTION: nsWindow statics
253 **************************************************************/
254 static const wchar_t kUser32LibName[] = L"user32.dll";
256 uint32_t nsWindow::sInstanceCount = 0;
257 bool nsWindow::sIsOleInitialized = false;
258 nsIWidget::Cursor nsWindow::sCurrentCursor = {};
259 nsWindow* nsWindow::sCurrentWindow = nullptr;
260 bool nsWindow::sJustGotDeactivate = false;
261 bool nsWindow::sJustGotActivate = false;
262 bool nsWindow::sIsInMouseCapture = false;
264 // Urgent-message reentrancy depth for the static `WindowProc` callback.
266 // Three unfortunate facts collide:
268 // 𝛼) Some messages must be processed promptly. If not, Windows will leave the
269 // receiving window in an intermediate, and potentially unusable, state until
270 // the WindowProc invocation that is handling it returns.
272 // 𝛽) Some messages have indefinitely long processing time. These are mostly
273 // messages which may cause us to enter a nested modal loop (via
274 // `SpinEventLoopUntil` or similar).
276 // 𝛾) Sometimes, messages skip the queue entirely. Our `WindowProc` may be
277 // reentrantly reinvoked from the kernel while we're blocking _on_ the
278 // kernel, even briefly, during processing of other messages. (Relevant
279 // search term: `KeUserModeCallback`.)
281 // The nightmare scenario, then, is that during processing of an 𝛼-message, we
282 // briefly become blocked (e.g., by calling `::SendMessageW()`), and the kernel
283 // takes that opportunity to use 𝛾 to hand us a 𝛽-message. (Concretely, see
284 // bug 1842170.)
286 // There is little we can do to prevent the first half of this scenario. 𝛼) and
287 // 𝛾) are effectively immutable facts of Windows, and we sometimes legitimately
288 // need to make blocking calls to process 𝛼-messages. (We may not even be aware
289 // that we're making such calls, if they're undocumented implementation details
290 // of another API.)
292 // In an ideal world, WindowProc would always return promptly (or at least in
293 // bounded time), and 𝛽-messages would not _per se_ exist; long-running modal
294 // states would instead be implemented in async fashion. In practice, that's far
295 // easier said than done -- replacing existing uses of `SpinEventLoopUntil` _et
296 // al._ with asynchronous mechanisms is a collection of mostly-unrelated cross-
297 // cutting architectural tasks, each of potentially unbounded scope. For now,
298 // and for the foreseeable future, we're stuck with them.
300 // We therefore simply punt. More specifically: if a known 𝛽-message jumps the
301 // queue to come in while we're in the middle of processing a known 𝛼-message,
302 // we:
303 // * properly queue the message for processing later;
304 // * respond to the 𝛽-message as though we actually had processed it; and
305 // * just hope that it can wait until we get around to it.
307 // The word "known" requires a bit of justification. There is no canonical set
308 // of 𝛼-messages, nor is the set of 𝛽-messages fixed (or even demarcable). We
309 // can't safely assume that all messages are 𝛼-messages, as that could cause
310 // 𝛽-messages to be arbitrarily and surprisingly delayed whenever any nested
311 // event loop is active. We also can't assume all messages are 𝛽-messages,
312 // since one 𝛼-message jumping the queue while processing another 𝛼-message is
313 // part of normal and required operation for windowed Windows applications.
315 // So we simply add messages to those sets as we identify them. (Or, preferably,
316 // rework the 𝛽-message's handling to make it no longer 𝛽. But see above.)
318 // ---
320 // The actual value of `sDepth` is the number of active invocations of
321 // `WindowProc` that are processing known 𝛼-messages.
322 size_t nsWindow::WndProcUrgentInvocation::sDepth = 0;
324 // Hook Data Members for Dropdowns. sProcessHook Tells the
325 // hook methods whether they should be processing the hook
326 // messages.
327 HHOOK nsWindow::sMsgFilterHook = nullptr;
328 HHOOK nsWindow::sCallProcHook = nullptr;
329 HHOOK nsWindow::sCallMouseHook = nullptr;
330 bool nsWindow::sProcessHook = false;
331 UINT nsWindow::sRollupMsgId = 0;
332 HWND nsWindow::sRollupMsgWnd = nullptr;
333 UINT nsWindow::sHookTimerId = 0;
335 // Used to prevent dispatching mouse events that do not originate from user
336 // input.
337 POINT nsWindow::sLastMouseMovePoint = {0};
339 bool nsWindow::sIsRestoringSession = false;
341 bool nsWindow::sTouchInjectInitialized = false;
342 InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
344 static SystemTimeConverter<DWORD>& TimeConverter() {
345 static SystemTimeConverter<DWORD> timeConverterSingleton;
346 return timeConverterSingleton;
349 // Global event hook for window cloaking. Never deregistered.
350 // - `Nothing` if not yet set.
351 // - `Some(nullptr)` if no attempt should be made to set it.
352 static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook = Nothing();
353 static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
355 namespace mozilla {
357 class CurrentWindowsTimeGetter {
358 public:
359 explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
361 DWORD GetCurrentTime() const { return ::GetTickCount(); }
363 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
364 DWORD currentTime = GetCurrentTime();
365 if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
366 // There's already one inflight with this timestamp. Don't
367 // send a duplicate.
368 return;
370 sBackwardsSkewStamp = Some(aNow);
371 sLastPostTime = currentTime;
372 static_assert(sizeof(WPARAM) >= sizeof(DWORD),
373 "Can't fit a DWORD in a WPARAM");
374 ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
377 static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
378 TimeStamp* aOutSkewStamp) {
379 if (aPostTime != sLastPostTime) {
380 // The SKEWFIX message is stale; we've sent a new one since then.
381 // Ignore this one.
382 return false;
384 MOZ_ASSERT(sBackwardsSkewStamp);
385 *aOutSkewStamp = sBackwardsSkewStamp.value();
386 sBackwardsSkewStamp = Nothing();
387 return true;
390 private:
391 static Maybe<TimeStamp> sBackwardsSkewStamp;
392 static DWORD sLastPostTime;
393 HWND mWnd;
396 Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
397 DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
399 } // namespace mozilla
401 /**************************************************************
403 * SECTION: globals variables
405 **************************************************************/
407 static const char* sScreenManagerContractID =
408 "@mozilla.org/gfx/screenmanager;1";
410 extern mozilla::LazyLogModule gWindowsLog;
412 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
414 // General purpose user32.dll hook object
415 static WindowsDllInterceptor sUser32Intercept;
417 // When the client area is extended out into the default window frame area,
418 // this is the minimum amount of space along the edge of resizable windows
419 // we will always display a resize cursor in, regardless of the underlying
420 // content.
421 static const int32_t kResizableBorderMinSize = 3;
423 // Getting this object from the window server can be expensive. Keep it
424 // around, also get it off the main thread. (See bug 1640852)
425 StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
426 static bool gInitializedVirtualDesktopManager = false;
428 // We should never really try to accelerate windows bigger than this. In some
429 // cases this might lead to no D3D9 acceleration where we could have had it
430 // but D3D9 does not reliably report when it supports bigger windows. 8192
431 // is as safe as we can get, we know at least D3D10 hardware always supports
432 // this, other hardware we expect to report correctly in D3D9.
433 #define MAX_ACCELERATED_DIMENSION 8192
435 // On window open (as well as after), Windows has an unfortunate habit of
436 // sending rather a lot of WM_NCHITTEST messages. Because we have to do point
437 // to DOM target conversions for these, we cache responses for a given
438 // coordinate this many milliseconds:
439 #define HITTEST_CACHE_LIFETIME_MS 50
441 #if defined(ACCESSIBILITY)
443 namespace mozilla {
446 * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
447 * injecting tiptsf.dll. The touchscreen process then posts registered messages
448 * to our main thread. The tiptsf hook picks up those registered messages and
449 * uses them as commands, some of which call into UIA, which then calls into
450 * MSAA, which then sends WM_GETOBJECT to us.
452 * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
453 * hook. Since thread-local hooks are called ahead of global hooks, we will
454 * see these registered messages before tiptsf does. At this point we can then
455 * raise a flag that blocks a11y before invoking CallNextHookEx which will then
456 * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
457 * flag by calling TIPMessageHandler::IsA11yBlocked().
459 * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
460 * function that also calls into UIA.
462 class TIPMessageHandler {
463 public:
464 ~TIPMessageHandler() {
465 if (mHook) {
466 ::UnhookWindowsHookEx(mHook);
470 static void Initialize() {
471 if (sInstance) {
472 return;
475 sInstance = new TIPMessageHandler();
476 ClearOnShutdown(&sInstance);
479 static bool IsA11yBlocked() {
480 if (!sInstance) {
481 return false;
484 return sInstance->mA11yBlockCount > 0;
487 private:
488 TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
489 MOZ_ASSERT(NS_IsMainThread());
491 // Registered messages used by tiptsf
492 mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
493 mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
494 mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
495 mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
496 mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
497 mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
498 mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
500 mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
501 ::GetCurrentThreadId());
502 MOZ_ASSERT(mHook);
504 if (!sSendMessageTimeoutWStub) {
505 sUser32Intercept.Init("user32.dll");
506 DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
507 sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
508 MOZ_ASSERT(hooked);
512 class MOZ_RAII A11yInstantiationBlocker {
513 public:
514 A11yInstantiationBlocker() {
515 if (!TIPMessageHandler::sInstance) {
516 return;
518 ++TIPMessageHandler::sInstance->mA11yBlockCount;
521 ~A11yInstantiationBlocker() {
522 if (!TIPMessageHandler::sInstance) {
523 return;
525 MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
526 --TIPMessageHandler::sInstance->mA11yBlockCount;
530 friend class A11yInstantiationBlocker;
532 static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
533 if (aCode < 0 || !sInstance) {
534 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
537 MSG* msg = reinterpret_cast<MSG*>(aLParam);
538 UINT& msgCode = msg->message;
540 for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
541 if (msgCode == sInstance->mMessages[i]) {
542 A11yInstantiationBlocker block;
543 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
547 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
550 static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
551 WPARAM aWParam, LPARAM aLParam,
552 UINT aFlags, UINT aTimeout,
553 PDWORD_PTR aMsgResult) {
554 // We don't want to handle this unless the message is a WM_GETOBJECT that we
555 // want to block, and the aHwnd is a nsWindow that belongs to the current
556 // (i.e., main) thread.
557 if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
558 static_cast<LONG>(aLParam) != OBJID_CLIENT || !::NS_IsMainThread() ||
559 !WinUtils::GetNSWindowPtr(aHwnd) || !IsA11yBlocked()) {
560 return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
561 aTimeout, aMsgResult);
564 // In this case we want to fake the result that would happen if we had
565 // decided not to handle WM_GETOBJECT in our WndProc. We hand the message
566 // off to DefWindowProc to accomplish this.
567 *aMsgResult = static_cast<DWORD_PTR>(
568 ::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
570 return static_cast<LRESULT>(TRUE);
573 static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
574 sSendMessageTimeoutWStub;
575 static StaticAutoPtr<TIPMessageHandler> sInstance;
577 HHOOK mHook;
578 UINT mMessages[7];
579 uint32_t mA11yBlockCount;
582 WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
583 TIPMessageHandler::sSendMessageTimeoutWStub;
584 StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
586 } // namespace mozilla
588 #endif // defined(ACCESSIBILITY)
590 namespace mozilla {
592 // This task will get the VirtualDesktopManager from the generic thread pool
593 // since doing this on the main thread on startup causes performance issues.
595 // See bug 1640852.
597 // This should be fine and should not require any locking, as when the main
598 // thread will access it, if it races with this function it will either find
599 // it to be null or to have a valid value.
600 class InitializeVirtualDesktopManagerTask : public Task {
601 public:
602 InitializeVirtualDesktopManagerTask()
603 : Task(Kind::OffMainThreadOnly, kDefaultPriorityValue) {}
605 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
606 bool GetName(nsACString& aName) override {
607 aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
608 return true;
610 #endif
612 virtual TaskResult Run() override {
613 RefPtr<IVirtualDesktopManager> desktopManager;
614 HRESULT hr = ::CoCreateInstance(
615 CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
616 __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
617 if (FAILED(hr)) {
618 return TaskResult::Complete;
621 gVirtualDesktopManager = desktopManager;
622 return TaskResult::Complete;
626 // Ground-truth query: does Windows claim the window is cloaked right now?
627 static bool IsCloaked(HWND hwnd) {
628 DWORD cloakedState;
629 HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
630 sizeof(cloakedState));
632 if (FAILED(hr)) {
633 MOZ_LOG(sCloakingLog, LogLevel::Warning,
634 ("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
635 return false;
638 return cloakedState != 0;
641 } // namespace mozilla
643 /**************************************************************
644 **************************************************************
646 ** BLOCK: nsIWidget impl.
648 ** nsIWidget interface implementation, broken down into
649 ** sections.
651 **************************************************************
652 **************************************************************/
654 /**************************************************************
656 * SECTION: nsWindow construction and destruction
658 **************************************************************/
660 nsWindow::nsWindow(bool aIsChildWindow)
661 : nsBaseWidget(BorderStyle::Default),
662 mBrush(::CreateSolidBrush(NSRGB_2_COLOREF(::GetSysColor(COLOR_BTNFACE)))),
663 mFrameState(std::in_place, this),
664 mIsChildWindow(aIsChildWindow),
665 mLastPaintEndTime(TimeStamp::Now()),
666 mCachedHitTestTime(TimeStamp::Now()),
667 mSizeConstraintsScale(GetDefaultScale().scale),
668 mDesktopId("DesktopIdMutex") {
669 MOZ_ASSERT(mWindowType == WindowType::Child);
671 if (!gInitializedVirtualDesktopManager) {
672 TaskController::Get()->AddTask(
673 MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
674 gInitializedVirtualDesktopManager = true;
677 // Global initialization
678 if (!sInstanceCount) {
679 // Global app registration id for Win7 and up. See
680 // WinTaskbar.cpp for details.
681 // MSIX packages explicitly do not support setting the appid from within
682 // the app, as it is set in the package manifest instead.
683 if (!WinUtils::HasPackageIdentity()) {
684 mozilla::widget::WinTaskbar::RegisterAppUserModelID();
686 if (!StaticPrefs::ui_key_layout_load_when_first_needed()) {
687 KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
689 #if defined(ACCESSIBILITY)
690 mozilla::TIPMessageHandler::Initialize();
691 #endif // defined(ACCESSIBILITY)
692 if (SUCCEEDED(::OleInitialize(nullptr))) {
693 sIsOleInitialized = true;
695 NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
696 MouseScrollHandler::Initialize();
697 // Init theme data
698 nsUXThemeData::UpdateNativeThemeInfo();
699 RedirectedKeyDownMessageManager::Forget();
700 } // !sInstanceCount
702 sInstanceCount++;
705 nsWindow::~nsWindow() {
706 mInDtor = true;
708 // If the widget was released without calling Destroy() then the native window
709 // still exists, and we need to destroy it. Destroy() will early-return if it
710 // was already called. In any case it is important to call it before
711 // destroying mPresentLock (cf. 1156182).
712 Destroy();
714 // Free app icon resources. This must happen after `OnDestroy` (see bug
715 // 708033).
716 if (mIconSmall) ::DestroyIcon(mIconSmall);
718 if (mIconBig) ::DestroyIcon(mIconBig);
720 sInstanceCount--;
722 // Global shutdown
723 if (sInstanceCount == 0) {
724 IMEHandler::Terminate();
725 sCurrentCursor = {};
726 if (sIsOleInitialized) {
727 ::OleFlushClipboard();
728 ::OleUninitialize();
729 sIsOleInitialized = false;
733 NS_IF_RELEASE(mNativeDragTarget);
736 /**************************************************************
738 * SECTION: nsIWidget::Create, nsIWidget::Destroy
740 * Creating and destroying windows for this widget.
742 **************************************************************/
744 // Allow Derived classes to modify the height that is passed
745 // when the window is created or resized.
746 int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
748 void nsWindow::SendAnAPZEvent(InputData& aEvent) {
749 LRESULT popupHandlingResult;
750 if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
751 // We need to consume the event after using it to roll up the popup(s).
752 return;
755 if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
756 // Give the swipe tracker a first pass at the event. If a new pan gesture
757 // has been started since the beginning of the swipe, the swipe tracker
758 // will know to ignore the event.
759 nsEventStatus status =
760 mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
761 if (status == nsEventStatus_eConsumeNoDefault) {
762 return;
766 APZEventResult result;
767 if (mAPZC) {
768 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
770 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
771 return;
774 MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
775 aEvent.mInputType == PINCHGESTURE_INPUT);
777 if (aEvent.mInputType == PANGESTURE_INPUT) {
778 PanGestureInput& panInput = aEvent.AsPanGestureInput();
779 WidgetWheelEvent event = panInput.ToWidgetEvent(this);
780 if (!mAPZC) {
781 if (MayStartSwipeForNonAPZ(panInput)) {
782 return;
784 } else {
785 event = MayStartSwipeForAPZ(panInput, result);
788 ProcessUntransformedAPZEvent(&event, result);
790 return;
793 PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
794 WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
795 ProcessUntransformedAPZEvent(&event, result);
798 void nsWindow::RecreateDirectManipulationIfNeeded() {
799 DestroyDirectManipulation();
801 if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
802 return;
805 if (!(StaticPrefs::apz_allow_zooming() ||
806 StaticPrefs::apz_windows_use_direct_manipulation()) ||
807 StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
808 return;
811 mDmOwner = MakeUnique<DirectManipulationOwner>(this);
813 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
814 GetHeight(mBounds.Height()));
815 mDmOwner->Init(bounds);
818 void nsWindow::ResizeDirectManipulationViewport() {
819 if (mDmOwner) {
820 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
821 GetHeight(mBounds.Height()));
822 mDmOwner->ResizeViewport(bounds);
826 void nsWindow::DestroyDirectManipulation() {
827 if (mDmOwner) {
828 mDmOwner->Destroy();
829 mDmOwner.reset();
833 // Create the proper widget
834 nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
835 const LayoutDeviceIntRect& aRect,
836 widget::InitData* aInitData) {
837 // Historical note: there was once some belief and/or intent that nsWindows
838 // could be created on arbitrary threads, and this may still be reflected in
839 // some comments.
840 MOZ_ASSERT(NS_IsMainThread());
842 widget::InitData defaultInitData;
843 if (!aInitData) aInitData = &defaultInitData;
845 nsIWidget* baseParent =
846 aInitData->mWindowType == WindowType::Dialog ||
847 aInitData->mWindowType == WindowType::TopLevel ||
848 aInitData->mWindowType == WindowType::Invisible
849 ? nullptr
850 : aParent;
852 mIsTopWidgetWindow = (nullptr == baseParent);
853 mBounds = aRect;
855 // Ensure that the toolkit is created.
856 nsToolkit::GetToolkit();
858 BaseCreate(baseParent, aInitData);
860 HWND parent;
861 if (aParent) { // has a nsIWidget parent
862 parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
863 mParent = aParent;
864 } else { // has a nsNative parent
865 parent = (HWND)aNativeParent;
866 mParent =
867 aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
870 mIsRTL = aInitData->mRTL;
871 mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
872 mAlwaysOnTop = aInitData->mAlwaysOnTop;
873 mIsAlert = aInitData->mIsAlert;
874 mResizable = aInitData->mResizable;
876 DWORD style = WindowStyle();
877 DWORD extendedStyle = WindowExStyle();
879 if (mWindowType == WindowType::Popup) {
880 if (!aParent) {
881 parent = nullptr;
883 } else if (mWindowType == WindowType::Invisible) {
884 // Make sure CreateWindowEx succeeds at creating a toplevel window
885 style &= ~0x40000000; // WS_CHILDWINDOW
886 } else {
887 // See if the caller wants to explictly set clip children and clip siblings
888 if (aInitData->mClipChildren) {
889 style |= WS_CLIPCHILDREN;
890 } else {
891 style &= ~WS_CLIPCHILDREN;
893 if (aInitData->mClipSiblings) {
894 style |= WS_CLIPSIBLINGS;
898 const wchar_t* className = ChooseWindowClass(mWindowType);
900 // Take specific actions when creating the first top-level window
901 static bool sFirstTopLevelWindowCreated = false;
902 if (aInitData->mWindowType == WindowType::TopLevel && !aParent &&
903 !sFirstTopLevelWindowCreated) {
904 sFirstTopLevelWindowCreated = true;
905 mWnd = ConsumePreXULSkeletonUIHandle();
906 auto skeletonUIError = GetPreXULSkeletonUIErrorReason();
907 if (skeletonUIError) {
908 nsAutoString errorString(
909 GetPreXULSkeletonUIErrorString(skeletonUIError.value()));
910 Telemetry::ScalarSet(
911 Telemetry::ScalarID::STARTUP_SKELETON_UI_DISABLED_REASON,
912 errorString);
914 if (mWnd) {
915 MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
916 "The skeleton UI window style should match the expected "
917 "style for the first window created");
918 MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
919 "The skeleton UI window extended style should match the "
920 "expected extended style for the first window created");
921 MOZ_ASSERT(
922 ::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
923 "The skeleton UI window should be created on the same thread as "
924 "other windows");
925 mIsShowingPreXULSkeletonUI = true;
927 // If we successfully consumed the pre-XUL skeleton UI, just update
928 // our internal state to match what is currently being displayed.
929 mIsVisible = true;
930 mIsCloaked = mozilla::IsCloaked(mWnd);
931 mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
933 // These match the margins set in browser-tabsintitlebar.js with
934 // default prefs on Windows. Bug 1673092 tracks lining this up with
935 // that more correctly instead of hard-coding it.
936 SetNonClientMargins(LayoutDeviceIntMargin(0, 2, 2, 2));
938 // Reset the WNDPROC for this window and its whole class, as we had
939 // to use our own WNDPROC when creating the the skeleton UI window.
940 ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
941 reinterpret_cast<LONG_PTR>(
942 WinUtils::NonClientDpiScalingDefWindowProcW));
943 ::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
944 reinterpret_cast<LONG_PTR>(
945 WinUtils::NonClientDpiScalingDefWindowProcW));
949 if (!mWnd) {
950 mWnd =
951 ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
952 aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
953 parent, nullptr, nsToolkit::mDllInstance, nullptr);
956 if (!mWnd) {
957 NS_WARNING("nsWindow CreateWindowEx failed.");
958 return NS_ERROR_FAILURE;
961 if (!sWinCloakEventHook) {
962 MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
964 // C++03 lambda approximation until P2173R1 is available (-std=c++2b)
965 struct StdcallLambda {
966 static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
967 DWORD event, HWND hwnd,
968 LONG idObject, LONG idChild,
969 DWORD idEventThread,
970 DWORD dwmsEventTime) {
971 const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
972 nsWindow::OnCloakEvent(hwnd, isCloaked);
976 const HWINEVENTHOOK hook = ::SetWinEventHook(
977 EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
978 &StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
979 ::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
980 sWinCloakEventHook = Some(hook);
982 if (!hook) {
983 const DWORD err = ::GetLastError();
984 MOZ_LOG(sCloakingLog, LogLevel::Error,
985 ("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
986 err));
990 if (aInitData->mIsPrivate) {
991 if (NimbusFeatures::GetBool("majorRelease2022"_ns,
992 "feltPrivacyWindowSeparation"_ns, true) &&
993 // Although permanent Private Browsing mode is indeed Private Browsing,
994 // we choose to make it look like regular Firefox in terms of the icon
995 // it uses (which also means we shouldn't use the Private Browsing
996 // AUMID).
997 !StaticPrefs::browser_privatebrowsing_autostart()) {
998 RefPtr<IPropertyStore> pPropStore;
999 if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
1000 getter_AddRefs(pPropStore)))) {
1001 PROPVARIANT pv;
1002 nsAutoString aumid;
1003 // make sure we're using the private browsing AUMID so that taskbar
1004 // grouping works properly
1005 Unused << NS_WARN_IF(
1006 !mozilla::widget::WinTaskbar::GenerateAppUserModelID(aumid, true));
1007 if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
1008 if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
1009 pPropStore->Commit();
1012 PropVariantClear(&pv);
1015 HICON icon = ::LoadIconW(::GetModuleHandleW(nullptr),
1016 MAKEINTRESOURCEW(IDI_PBMODE));
1017 SetBigIcon(icon);
1018 SetSmallIcon(icon);
1022 mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
1024 // If mDefaultScale is set before mWnd has been set, it will have the scale of
1025 // the primary monitor, rather than the monitor that the window is actually
1026 // on. For non-popup windows this gets corrected by the WM_DPICHANGED message
1027 // which resets mDefaultScale, but for popup windows we don't reset
1028 // mDefaultScale on that message. In order to ensure that popup windows
1029 // spawned on a non-primary monitor end up with the correct scale, we reset
1030 // mDefaultScale here so that it gets recomputed using the correct monitor now
1031 // that we have a mWnd.
1032 mDefaultScale = -1.0;
1034 if (mIsRTL) {
1035 DWORD dwAttribute = TRUE;
1036 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1037 sizeof dwAttribute);
1040 UpdateDarkModeToolbar();
1042 if (mOpeningAnimationSuppressed) {
1043 SuppressAnimation(true);
1046 if (mAlwaysOnTop) {
1047 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
1048 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1051 if (mWindowType != WindowType::Invisible &&
1052 MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
1053 // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
1055 // We create two zero-sized windows as descendants of the top-level window,
1056 // like so:
1058 // Top-level window (MozillaWindowClass)
1059 // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
1060 // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
1062 // We need to have the middle window, otherwise the Trackpoint driver
1063 // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
1064 // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
1065 // window hierarchy until they are handled by nsWindow::WindowProc.
1066 // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
1067 // but these do not propagate automatically, so we have the window
1068 // procedure pretend that they were dispatched to the top-level window
1069 // instead.
1071 // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
1072 // is given below so that it catches the Trackpoint driver's heuristics.
1073 HWND scrollContainerWnd = ::CreateWindowW(
1074 className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
1075 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1076 HWND scrollableWnd = ::CreateWindowW(
1077 className, L"FAKETRACKPOINTSCROLLABLE",
1078 WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
1079 scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1081 // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
1082 // WindowProcInternal can distinguish it from the top-level window
1083 // easily.
1084 ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
1086 // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
1087 // old window procedure in its "user data".
1088 WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
1089 scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
1090 ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
1093 // We will start receiving native events after associating with our native
1094 // window. We will also become the output of WinUtils::GetNSWindowPtr for that
1095 // window.
1096 if (!AssociateWithNativeWindow()) {
1097 return NS_ERROR_FAILURE;
1100 // Starting with Windows XP, a process always runs within a terminal services
1101 // session. In order to play nicely with RDP, fast user switching, and the
1102 // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
1103 // our HWND in order to receive this message.
1104 DebugOnly<BOOL> wtsRegistered =
1105 ::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
1106 NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
1108 mDefaultIMC.Init(this);
1109 IMEHandler::InitInputContext(this, mInputContext);
1111 static bool a11yPrimed = false;
1112 if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
1113 a11yPrimed = true;
1114 if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
1115 ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
1119 RecreateDirectManipulationIfNeeded();
1121 return NS_OK;
1124 void nsWindow::LocalesChanged() {
1125 bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
1126 if (mIsRTL != isRTL) {
1127 DWORD dwAttribute = isRTL;
1128 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1129 sizeof dwAttribute);
1130 mIsRTL = isRTL;
1134 // Close this nsWindow
1135 void nsWindow::Destroy() {
1136 // WM_DESTROY has already fired, avoid calling it twice
1137 if (mOnDestroyCalled) return;
1139 // Don't destroy windows that have file pickers open, we'll tear these down
1140 // later once the picker is closed.
1141 mDestroyCalled = true;
1142 if (mPickerDisplayCount) return;
1144 // During the destruction of all of our children, make sure we don't get
1145 // deleted.
1146 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1148 DestroyDirectManipulation();
1151 * On windows the LayerManagerOGL destructor wants the widget to be around for
1152 * cleanup. It also would like to have the HWND intact, so we nullptr it here.
1154 DestroyLayerManager();
1156 InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
1157 mDeviceNotifyHandle = nullptr;
1159 // The DestroyWindow function destroys the specified window. The function
1160 // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
1161 // and remove the keyboard focus from it. The function also destroys the
1162 // window's menu, flushes the thread message queue, destroys timers, removes
1163 // clipboard ownership, and breaks the clipboard viewer chain (if the window
1164 // is at the top of the viewer chain).
1166 // If the specified window is a parent or owner window, DestroyWindow
1167 // automatically destroys the associated child or owned windows when it
1168 // destroys the parent or owner window. The function first destroys child or
1169 // owned windows, and then it destroys the parent or owner window.
1170 VERIFY(::DestroyWindow(mWnd));
1172 // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
1173 // OnDestroy() didn't get called, call it now.
1174 if (false == mOnDestroyCalled) {
1175 MSGResult msgResult;
1176 mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
1177 OnDestroy();
1181 /**************************************************************
1183 * SECTION: Window class utilities
1185 * Utilities for calculating the proper window class name for
1186 * Create window.
1188 **************************************************************/
1190 /* static */
1191 const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
1192 UINT aExtraStyle, LPWSTR aIconID) {
1193 WNDCLASSW wc;
1194 if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
1195 // already registered
1196 return aClassName;
1199 wc.style = CS_DBLCLKS | aExtraStyle;
1200 wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
1201 wc.cbClsExtra = 0;
1202 wc.cbWndExtra = 0;
1203 wc.hInstance = nsToolkit::mDllInstance;
1204 wc.hIcon =
1205 aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
1206 wc.hCursor = nullptr;
1207 wc.hbrBackground = nullptr;
1208 wc.lpszMenuName = nullptr;
1209 wc.lpszClassName = aClassName;
1211 if (!::RegisterClassW(&wc)) {
1212 // For older versions of Win32 (i.e., not XP), the registration may
1213 // fail with aExtraStyle, so we have to re-register without it.
1214 wc.style = CS_DBLCLKS;
1215 ::RegisterClassW(&wc);
1217 return aClassName;
1220 static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
1222 /* static */
1223 const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) {
1224 const wchar_t* className = [aWindowType] {
1225 switch (aWindowType) {
1226 case WindowType::Invisible:
1227 return kClassNameHidden;
1228 case WindowType::Dialog:
1229 return kClassNameDialog;
1230 case WindowType::Popup:
1231 return kClassNameDropShadow;
1232 default:
1233 return GetMainWindowClass();
1235 }();
1236 return RegisterWindowClass(className, 0, gStockApplicationIcon);
1239 /**************************************************************
1241 * SECTION: Window styles utilities
1243 * Return the proper windows styles and extended styles.
1245 **************************************************************/
1247 // Return nsWindow styles
1248 DWORD nsWindow::WindowStyle() {
1249 DWORD style;
1251 switch (mWindowType) {
1252 case WindowType::Child:
1253 style = WS_OVERLAPPED;
1254 break;
1256 case WindowType::Dialog:
1257 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
1258 DS_MODALFRAME | WS_CLIPCHILDREN;
1259 if (mBorderStyle != BorderStyle::Default)
1260 style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
1261 break;
1263 case WindowType::Popup:
1264 style = WS_POPUP | WS_OVERLAPPED;
1265 break;
1267 default:
1268 NS_ERROR("unknown border style");
1269 [[fallthrough]];
1271 case WindowType::TopLevel:
1272 case WindowType::Invisible:
1273 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
1274 WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
1275 break;
1278 if (mBorderStyle != BorderStyle::Default &&
1279 mBorderStyle != BorderStyle::All) {
1280 if (mBorderStyle == BorderStyle::None ||
1281 !(mBorderStyle & BorderStyle::Border))
1282 style &= ~WS_BORDER;
1284 if (mBorderStyle == BorderStyle::None ||
1285 !(mBorderStyle & BorderStyle::Title)) {
1286 style &= ~WS_DLGFRAME;
1289 if (mBorderStyle == BorderStyle::None ||
1290 !(mBorderStyle & BorderStyle::Close))
1291 style &= ~0;
1292 // XXX The close box can only be removed by changing the window class,
1293 // as far as I know --- roc+moz@cs.cmu.edu
1295 if (mBorderStyle == BorderStyle::None ||
1296 !(mBorderStyle & (BorderStyle::Menu | BorderStyle::Close)))
1297 style &= ~WS_SYSMENU;
1298 // Looks like getting rid of the system menu also does away with the
1299 // close box. So, we only get rid of the system menu if you want neither it
1300 // nor the close box. How does the Windows "Dialog" window class get just
1301 // closebox and no sysmenu? Who knows.
1303 if (mBorderStyle == BorderStyle::None ||
1304 !(mBorderStyle & BorderStyle::ResizeH))
1305 style &= ~WS_THICKFRAME;
1307 if (mBorderStyle == BorderStyle::None ||
1308 !(mBorderStyle & BorderStyle::Minimize))
1309 style &= ~WS_MINIMIZEBOX;
1311 if (mBorderStyle == BorderStyle::None ||
1312 !(mBorderStyle & BorderStyle::Maximize))
1313 style &= ~WS_MAXIMIZEBOX;
1316 if (mIsChildWindow) {
1317 style |= WS_CLIPCHILDREN;
1318 if (!(style & WS_POPUP)) {
1319 style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
1323 VERIFY_WINDOW_STYLE(style);
1324 return style;
1327 // Return nsWindow extended styles
1328 DWORD nsWindow::WindowExStyle() {
1329 switch (mWindowType) {
1330 case WindowType::Child:
1331 return 0;
1332 case WindowType::Popup: {
1333 DWORD extendedStyle = WS_EX_TOOLWINDOW;
1334 if (mPopupLevel == PopupLevel::Top) {
1335 extendedStyle |= WS_EX_TOPMOST;
1337 return extendedStyle;
1339 case WindowType::Dialog:
1340 case WindowType::TopLevel:
1341 case WindowType::Invisible:
1342 break;
1344 if (mIsAlert) {
1345 MOZ_ASSERT(mWindowType == WindowType::Dialog,
1346 "Expect alert windows to have type=dialog");
1347 return WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
1349 return WS_EX_WINDOWEDGE;
1352 /**************************************************************
1354 * SECTION: Native window association utilities
1356 * Used in Create and Destroy. A nsWindow can associate with its
1357 * underlying native window mWnd. Once a native window is
1358 * associated with a nsWindow, its native events will be handled
1359 * by the static member function nsWindow::WindowProc. Moreover,
1360 * the association will be registered in the WinUtils association
1361 * list, that is, calling WinUtils::GetNSWindowPtr on the native
1362 * window will return the associated nsWindow. This is used in
1363 * nsWindow::WindowProc to correctly dispatch native events to
1364 * the handler methods defined in nsWindow, even though it is a
1365 * static member function.
1367 * After dissociation, the native events of the native window will
1368 * no longer be handled by nsWindow::WindowProc, and will thus not
1369 * be dispatched to the nsWindow native event handler methods.
1370 * Moreover, the association will no longer be registered in the
1371 * WinUtils association list, so calling WinUtils::GetNSWindowPtr
1372 * on the native window will return nullptr.
1374 **************************************************************/
1376 bool nsWindow::AssociateWithNativeWindow() {
1377 if (!mWnd || !IsWindow(mWnd)) {
1378 NS_ERROR("Invalid window handle");
1379 return false;
1382 // Connect the this pointer to the native window handle.
1383 // This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
1384 // uses WinUtils::GetNSWindowPtr internally.
1385 WinUtils::SetNSWindowPtr(mWnd, this);
1387 ::SetLastError(ERROR_SUCCESS);
1388 const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1389 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
1390 if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
1391 NS_ERROR("Failure in SetWindowLongPtrW");
1392 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1393 return false;
1396 mPrevWndProc.emplace(prevWndProc);
1397 return true;
1400 void nsWindow::DissociateFromNativeWindow() {
1401 if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
1402 return;
1405 DebugOnly<WNDPROC> wndProcBeforeDissociate =
1406 reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1407 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
1408 NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
1409 "Unstacked an unexpected native window procedure");
1411 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1412 mPrevWndProc.reset();
1415 /**************************************************************
1417 * SECTION: nsIWidget::SetParent, nsIWidget::GetParent
1419 * Set or clear the parent widgets using window properties, and
1420 * handles calculating native parent handles.
1422 **************************************************************/
1424 // Get and set parent widgets
1425 void nsWindow::SetParent(nsIWidget* aNewParent) {
1426 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1427 nsIWidget* parent = GetParent();
1428 if (parent) {
1429 parent->RemoveChild(this);
1432 mParent = aNewParent;
1434 if (aNewParent) {
1435 ReparentNativeWidget(aNewParent);
1436 aNewParent->AddChild(this);
1437 return;
1439 if (mWnd) {
1440 // If we have no parent, SetParent should return the desktop.
1441 VERIFY(::SetParent(mWnd, nullptr));
1442 RecreateDirectManipulationIfNeeded();
1446 void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
1447 MOZ_ASSERT(aNewParent, "null widget");
1449 mParent = aNewParent;
1450 if (mWindowType == WindowType::Popup) {
1451 return;
1453 HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
1454 NS_ASSERTION(newParent, "Parent widget has a null native window handle");
1455 if (newParent && mWnd) {
1456 ::SetParent(mWnd, newParent);
1457 RecreateDirectManipulationIfNeeded();
1461 nsIWidget* nsWindow::GetParent(void) {
1462 if (mIsTopWidgetWindow) {
1463 return nullptr;
1465 if (mInDtor || mOnDestroyCalled) {
1466 return nullptr;
1468 return mParent;
1471 static int32_t RoundDown(double aDouble) {
1472 return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
1473 : static_cast<int32_t>(ceil(aDouble));
1476 float nsWindow::GetDPI() {
1477 float dpi = 96.0f;
1478 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
1479 if (screen) {
1480 screen->GetDpi(&dpi);
1482 return dpi;
1485 double nsWindow::GetDefaultScaleInternal() {
1486 if (mDefaultScale <= 0.0) {
1487 mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
1489 return mDefaultScale;
1492 int32_t nsWindow::LogToPhys(double aValue) {
1493 return WinUtils::LogToPhys(
1494 ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
1497 nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
1498 return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
1501 nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
1502 if (mIsTopWidgetWindow) {
1503 // Must use a flag instead of mWindowType to tell if the window is the
1504 // owned by the topmost widget, because a child window can be embedded
1505 // inside a HWND which is not associated with a nsIWidget.
1506 return nullptr;
1509 // If this widget has already been destroyed, pretend we have no parent.
1510 // This corresponds to code in Destroy which removes the destroyed
1511 // widget from its parent's child list.
1512 if (mInDtor || mOnDestroyCalled) return nullptr;
1514 // aIncludeOwner set to true implies walking the parent chain to retrieve the
1515 // root owner. aIncludeOwner set to false implies the search will stop at the
1516 // true parent (default).
1517 nsWindow* widget = nullptr;
1518 if (mWnd) {
1519 HWND parent = nullptr;
1520 if (aIncludeOwner)
1521 parent = ::GetParent(mWnd);
1522 else
1523 parent = ::GetAncestor(mWnd, GA_PARENT);
1525 if (parent) {
1526 widget = WinUtils::GetNSWindowPtr(parent);
1527 if (widget) {
1528 // If the widget is in the process of being destroyed then
1529 // do NOT return it
1530 if (widget->mInDtor) {
1531 widget = nullptr;
1537 return widget;
1540 /**************************************************************
1542 * SECTION: nsIWidget::Show
1544 * Hide or show this component.
1546 **************************************************************/
1548 void nsWindow::Show(bool bState) {
1549 if (bState && mIsShowingPreXULSkeletonUI) {
1550 // The first time we decide to actually show the window is when we decide
1551 // that we've taken over the window from the skeleton UI, and we should
1552 // no longer treat resizes / moves specially.
1553 mIsShowingPreXULSkeletonUI = false;
1554 #if defined(ACCESSIBILITY)
1555 // If our HWND has focus and the a11y engine hasn't started yet, fire a
1556 // focus win event. Windows already did this when the skeleton UI appeared,
1557 // but a11y wouldn't have been able to start at that point even if a client
1558 // responded. Firing this now gives clients the chance to respond with
1559 // WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
1560 // this if the a11y engine has already started because it has probably
1561 // already fired focus on a descendant.
1562 if (::GetFocus() == mWnd && !GetAccService()) {
1563 ::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
1565 #endif // defined(ACCESSIBILITY)
1568 if (mWindowType == WindowType::Popup) {
1569 MOZ_ASSERT(ChooseWindowClass(mWindowType) == kClassNameDropShadow);
1570 // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
1571 // some popup menus to become invisible.
1572 LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
1573 if (exStyle & WS_EX_LAYERED) {
1574 ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
1578 bool syncInvalidate = false;
1580 bool wasVisible = mIsVisible;
1581 // Set the status now so that anyone asking during ShowWindow or
1582 // SetWindowPos would get the correct answer.
1583 mIsVisible = bState;
1585 // We may have cached an out of date visible state. This can happen
1586 // when session restore sets the full screen mode.
1587 if (mIsVisible)
1588 mOldStyle |= WS_VISIBLE;
1589 else
1590 mOldStyle &= ~WS_VISIBLE;
1592 if (mWnd) {
1593 if (bState) {
1594 if (!wasVisible && mWindowType == WindowType::TopLevel) {
1595 // speed up the initial paint after show for
1596 // top level windows:
1597 syncInvalidate = true;
1599 // Set the cursor before showing the window to avoid the default wait
1600 // cursor.
1601 SetCursor(Cursor{eCursor_standard});
1603 switch (mFrameState->GetSizeMode()) {
1604 case nsSizeMode_Fullscreen:
1605 ::ShowWindow(mWnd, SW_SHOW);
1606 break;
1607 case nsSizeMode_Maximized:
1608 ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
1609 break;
1610 case nsSizeMode_Minimized:
1611 ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
1612 break;
1613 default:
1614 if (CanTakeFocus() && !mAlwaysOnTop) {
1615 ::ShowWindow(mWnd, SW_SHOWNORMAL);
1616 } else {
1617 ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
1618 // Don't flicker the window if we're restoring session
1619 if (!sIsRestoringSession) {
1620 Unused << GetAttention(2);
1623 break;
1625 } else {
1626 DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
1627 if (wasVisible) {
1628 flags |= SWP_NOZORDER;
1630 if (mAlwaysOnTop || mIsAlert) {
1631 flags |= SWP_NOACTIVATE;
1634 if (mWindowType == WindowType::Popup) {
1635 // ensure popups are the topmost of the TOPMOST
1636 // layer. Remember not to set the SWP_NOZORDER
1637 // flag as that might allow the taskbar to overlap
1638 // the popup.
1639 flags |= SWP_NOACTIVATE;
1640 HWND owner = ::GetWindow(mWnd, GW_OWNER);
1641 if (owner) {
1642 // PopupLevel::Top popups should be above all else. All other
1643 // types should be placed in front of their owner, without
1644 // changing the owner's z-level relative to other windows.
1645 if (mPopupLevel != PopupLevel::Top) {
1646 ::SetWindowPos(mWnd, owner, 0, 0, 0, 0, flags);
1647 ::SetWindowPos(owner, mWnd, 0, 0, 0, 0,
1648 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1649 } else {
1650 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1652 } else {
1653 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
1655 } else {
1656 if (mWindowType == WindowType::Dialog && !CanTakeFocus())
1657 flags |= SWP_NOACTIVATE;
1659 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1662 } else {
1663 // Clear contents to avoid ghosting of old content if we display
1664 // this window again.
1665 if (wasVisible && mTransparencyMode == TransparencyMode::Transparent) {
1666 if (mCompositorWidgetDelegate) {
1667 mCompositorWidgetDelegate->ClearTransparentWindow();
1670 if (mWindowType != WindowType::Dialog) {
1671 ::ShowWindow(mWnd, SW_HIDE);
1672 } else {
1673 ::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
1674 SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
1675 SWP_NOACTIVATE);
1680 if (!wasVisible && bState) {
1681 Invalidate();
1682 if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
1683 ::UpdateWindow(mWnd);
1687 if (mOpeningAnimationSuppressed) {
1688 SuppressAnimation(false);
1692 /**************************************************************
1694 * SECTION: nsIWidget::IsVisible
1696 * Returns the visibility state.
1698 **************************************************************/
1700 // Return true if the component is visible, false otherwise.
1702 // This does not take cloaking into account.
1703 bool nsWindow::IsVisible() const { return mIsVisible; }
1705 /**************************************************************
1707 * SECTION: Touch and APZ-related functions
1709 **************************************************************/
1711 void nsWindow::RegisterTouchWindow() {
1712 mTouchWindow = true;
1713 ::RegisterTouchWindow(mWnd, TWF_WANTPALM);
1714 ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
1717 BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
1718 nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
1719 if (win) {
1720 ::RegisterTouchWindow(aWnd, TWF_WANTPALM);
1722 return TRUE;
1725 void nsWindow::LockAspectRatio(bool aShouldLock) {
1726 if (aShouldLock) {
1727 mAspectRatio = (float)mBounds.Width() / (float)mBounds.Height();
1728 } else {
1729 mAspectRatio = 0.0;
1733 /**************************************************************
1735 * SECTION: nsIWidget::SetInputRegion
1737 * Sets whether the window should ignore mouse events.
1739 **************************************************************/
1740 void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
1741 mInputRegion = aInputRegion;
1744 /**************************************************************
1746 * SECTION: nsIWidget::Move, nsIWidget::Resize, nsIWidget::Size
1748 * Repositioning and sizing a window.
1750 **************************************************************/
1752 void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1753 SizeConstraints c = aConstraints;
1755 if (mWindowType != WindowType::Popup && mResizable) {
1756 c.mMinSize.width =
1757 std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
1758 c.mMinSize.height =
1759 std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
1762 if (mMaxTextureSize > 0) {
1763 // We can't make ThebesLayers bigger than this anyway.. no point it letting
1764 // a window grow bigger as we won't be able to draw content there in
1765 // general.
1766 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
1767 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
1770 mSizeConstraintsScale = GetDefaultScale().scale;
1772 nsBaseWidget::SetSizeConstraints(c);
1775 const SizeConstraints nsWindow::GetSizeConstraints() {
1776 double scale = GetDefaultScale().scale;
1777 if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
1778 return mSizeConstraints;
1780 scale /= mSizeConstraintsScale;
1781 SizeConstraints c = mSizeConstraints;
1782 if (c.mMinSize.width != NS_MAXSIZE) {
1783 c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
1785 if (c.mMinSize.height != NS_MAXSIZE) {
1786 c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
1788 if (c.mMaxSize.width != NS_MAXSIZE) {
1789 c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
1791 if (c.mMaxSize.height != NS_MAXSIZE) {
1792 c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
1794 return c;
1797 // Move this component
1798 void nsWindow::Move(double aX, double aY) {
1799 if (mWindowType == WindowType::TopLevel ||
1800 mWindowType == WindowType::Dialog) {
1801 SetSizeMode(nsSizeMode_Normal);
1804 // for top-level windows only, convert coordinates from desktop pixels
1805 // (the "parent" coordinate space) to the window's device pixel space
1806 double scale =
1807 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1808 int32_t x = NSToIntRound(aX * scale);
1809 int32_t y = NSToIntRound(aY * scale);
1811 // Check to see if window needs to be moved first
1812 // to avoid a costly call to SetWindowPos. This check
1813 // can not be moved to the calling code in nsView, because
1814 // some platforms do not position child windows correctly
1816 // Only perform this check for non-popup windows, since the positioning can
1817 // in fact change even when the x/y do not. We always need to perform the
1818 // check. See bug #97805 for details.
1819 if (mWindowType != WindowType::Popup && mBounds.IsEqualXY(x, y)) {
1820 // Nothing to do, since it is already positioned correctly.
1821 return;
1824 mBounds.MoveTo(x, y);
1826 if (mWnd) {
1827 #ifdef DEBUG
1828 // complain if a window is moved offscreen (legal, but potentially
1829 // worrisome)
1830 if (mIsTopWidgetWindow) { // only a problem for top-level windows
1831 // Make sure this window is actually on the screen before we move it
1832 // XXX: Needs multiple monitor support
1833 HDC dc = ::GetDC(mWnd);
1834 if (dc) {
1835 if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
1836 RECT workArea;
1837 ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
1838 // no annoying assertions. just mention the issue.
1839 if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) {
1840 MOZ_LOG(gWindowsLog, LogLevel::Info,
1841 ("window moved to offscreen position\n"));
1844 ::ReleaseDC(mWnd, dc);
1847 #endif
1849 // Normally, when the skeleton UI is disabled, we resize+move the window
1850 // before showing it in order to ensure that it restores to the correct
1851 // position when the user un-maximizes it. However, when we are using the
1852 // skeleton UI, this results in the skeleton UI window being moved around
1853 // undesirably before being locked back into the maximized position. To
1854 // avoid this, we simply set the placement to restore to via
1855 // SetWindowPlacement. It's a little bit more of a dance, though, since we
1856 // need to convert the workspace coords that SetWindowPlacement uses to the
1857 // screen space coordinates we normally use with SetWindowPos.
1858 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1859 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1860 VERIFY(::GetWindowPlacement(mWnd, &pl));
1862 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
1863 if (NS_WARN_IF(!monitor)) {
1864 return;
1866 MONITORINFO mi = {sizeof(MONITORINFO)};
1867 VERIFY(::GetMonitorInfo(monitor, &mi));
1869 int32_t deltaX =
1870 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
1871 int32_t deltaY =
1872 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
1873 pl.rcNormalPosition.left += deltaX;
1874 pl.rcNormalPosition.right += deltaX;
1875 pl.rcNormalPosition.top += deltaY;
1876 pl.rcNormalPosition.bottom += deltaY;
1877 VERIFY(::SetWindowPlacement(mWnd, &pl));
1878 } else {
1879 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
1880 double oldScale = mDefaultScale;
1881 mResizeState = IN_SIZEMOVE;
1882 VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
1883 mResizeState = NOT_RESIZING;
1884 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
1885 ChangedDPI();
1889 ResizeDirectManipulationViewport();
1893 // Resize this component
1894 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1895 // for top-level windows only, convert coordinates from desktop pixels
1896 // (the "parent" coordinate space) to the window's device pixel space
1897 double scale =
1898 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1899 int32_t width = NSToIntRound(aWidth * scale);
1900 int32_t height = NSToIntRound(aHeight * scale);
1902 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
1903 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
1904 if (width < 0 || height < 0) {
1905 gfxCriticalNoteOnce << "Negative passed to Resize(" << width << ", "
1906 << height << ") repaint: " << aRepaint;
1909 ConstrainSize(&width, &height);
1911 // Avoid unnecessary resizing calls
1912 if (mBounds.IsEqualSize(width, height)) {
1913 if (aRepaint) {
1914 Invalidate();
1916 return;
1919 // Set cached value for lightweight and printing
1920 bool wasLocking = mAspectRatio != 0.0;
1921 mBounds.SizeTo(width, height);
1922 if (wasLocking) {
1923 LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
1926 if (mWnd) {
1927 // Refer to the comment above a similar check in nsWindow::Move
1928 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1929 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1930 VERIFY(::GetWindowPlacement(mWnd, &pl));
1931 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
1932 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
1933 mResizeState = RESIZING;
1934 VERIFY(::SetWindowPlacement(mWnd, &pl));
1935 mResizeState = NOT_RESIZING;
1936 } else {
1937 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
1939 if (!aRepaint) {
1940 flags |= SWP_NOREDRAW;
1943 double oldScale = mDefaultScale;
1944 mResizeState = RESIZING;
1945 VERIFY(
1946 ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags));
1948 mResizeState = NOT_RESIZING;
1949 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
1950 ChangedDPI();
1954 ResizeDirectManipulationViewport();
1957 if (aRepaint) Invalidate();
1960 // Resize this component
1961 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
1962 bool aRepaint) {
1963 // for top-level windows only, convert coordinates from desktop pixels
1964 // (the "parent" coordinate space) to the window's device pixel space
1965 double scale =
1966 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1967 int32_t x = NSToIntRound(aX * scale);
1968 int32_t y = NSToIntRound(aY * scale);
1969 int32_t width = NSToIntRound(aWidth * scale);
1970 int32_t height = NSToIntRound(aHeight * scale);
1972 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
1973 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
1974 if (width < 0 || height < 0) {
1975 gfxCriticalNoteOnce << "Negative passed to Resize(" << x << " ," << y
1976 << ", " << width << ", " << height
1977 << ") repaint: " << aRepaint;
1980 ConstrainSize(&width, &height);
1982 // Avoid unnecessary resizing calls
1983 if (mBounds.IsEqualRect(x, y, width, height)) {
1984 if (aRepaint) {
1985 Invalidate();
1987 return;
1990 // Set cached value for lightweight and printing
1991 mBounds.SetRect(x, y, width, height);
1993 if (mWnd) {
1994 // Refer to the comment above a similar check in nsWindow::Move
1995 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1996 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1997 VERIFY(::GetWindowPlacement(mWnd, &pl));
1999 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2000 if (NS_WARN_IF(!monitor)) {
2001 return;
2003 MONITORINFO mi = {sizeof(MONITORINFO)};
2004 VERIFY(::GetMonitorInfo(monitor, &mi));
2006 int32_t deltaX =
2007 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
2008 int32_t deltaY =
2009 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
2010 pl.rcNormalPosition.left += deltaX;
2011 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
2012 pl.rcNormalPosition.top += deltaY;
2013 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
2014 VERIFY(::SetWindowPlacement(mWnd, &pl));
2015 } else {
2016 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
2017 if (!aRepaint) {
2018 flags |= SWP_NOREDRAW;
2021 double oldScale = mDefaultScale;
2022 mResizeState = RESIZING;
2023 VERIFY(
2024 ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags));
2025 mResizeState = NOT_RESIZING;
2026 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
2027 ChangedDPI();
2030 if (mTransitionWnd) {
2031 // If we have a fullscreen transition window, we need to make
2032 // it topmost again, otherwise the taskbar may be raised by
2033 // the system unexpectedly when we leave fullscreen state.
2034 ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
2035 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
2039 ResizeDirectManipulationViewport();
2042 if (aRepaint) Invalidate();
2045 mozilla::Maybe<bool> nsWindow::IsResizingNativeWidget() {
2046 if (mResizeState == RESIZING) {
2047 return Some(true);
2049 return Some(false);
2052 /**************************************************************
2054 * SECTION: Window Z-order and state.
2056 * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode,
2057 * nsIWidget::ConstrainPosition
2059 * Z-order, positioning, restore, minimize, and maximize.
2061 **************************************************************/
2063 // Position the window behind the given window
2064 void nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
2065 nsIWidget* aWidget, bool aActivate) {
2066 HWND behind = HWND_TOP;
2067 if (aPlacement == eZPlacementBottom)
2068 behind = HWND_BOTTOM;
2069 else if (aPlacement == eZPlacementBelow && aWidget)
2070 behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
2071 UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE;
2072 if (!aActivate) flags |= SWP_NOACTIVATE;
2074 if (!CanTakeFocus() && behind == HWND_TOP) {
2075 // Can't place the window to top so place it behind the foreground window
2076 // (as long as it is not topmost)
2077 HWND wndAfter = ::GetForegroundWindow();
2078 if (!wndAfter)
2079 behind = HWND_BOTTOM;
2080 else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST))
2081 behind = wndAfter;
2082 flags |= SWP_NOACTIVATE;
2085 ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
2088 static UINT GetCurrentShowCmd(HWND aWnd) {
2089 WINDOWPLACEMENT pl;
2090 pl.length = sizeof(pl);
2091 ::GetWindowPlacement(aWnd, &pl);
2092 return pl.showCmd;
2095 // Maximize, minimize or restore the window.
2096 void nsWindow::SetSizeMode(nsSizeMode aMode) {
2097 // If we are still displaying a maximized pre-XUL skeleton UI, ignore the
2098 // noise of sizemode changes. Once we have "shown" the window for the first
2099 // time (called nsWindow::Show(true), even though the window is already
2100 // technically displayed), we will again accept sizemode changes.
2101 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2102 return;
2105 mFrameState->EnsureSizeMode(aMode);
2108 nsSizeMode nsWindow::SizeMode() { return mFrameState->GetSizeMode(); }
2110 void DoGetWorkspaceID(HWND aWnd, nsAString* aWorkspaceID) {
2111 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2112 if (!desktopManager || !aWnd) {
2113 return;
2116 GUID desktop;
2117 HRESULT hr = desktopManager->GetWindowDesktopId(aWnd, &desktop);
2118 if (FAILED(hr)) {
2119 return;
2122 RPC_WSTR workspaceIDStr = nullptr;
2123 if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
2124 aWorkspaceID->Assign((wchar_t*)workspaceIDStr);
2125 RpcStringFreeW(&workspaceIDStr);
2129 void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
2130 // If we have a value cached, use that, but also make sure it is
2131 // scheduled to be updated. If we don't yet have a value, get
2132 // one synchronously.
2133 auto desktop = mDesktopId.Lock();
2134 if (desktop->mID.IsEmpty()) {
2135 DoGetWorkspaceID(mWnd, &desktop->mID);
2136 desktop->mUpdateIsQueued = false;
2137 } else {
2138 AsyncUpdateWorkspaceID(*desktop);
2141 workspaceID = desktop->mID;
2144 void nsWindow::AsyncUpdateWorkspaceID(Desktop& aDesktop) {
2145 struct UpdateWorkspaceIdTask : public Task {
2146 explicit UpdateWorkspaceIdTask(nsWindow* aSelf)
2147 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
2148 mSelf(aSelf) {}
2150 TaskResult Run() override {
2151 auto desktop = mSelf->mDesktopId.Lock();
2152 if (desktop->mUpdateIsQueued) {
2153 DoGetWorkspaceID(mSelf->mWnd, &desktop->mID);
2154 desktop->mUpdateIsQueued = false;
2156 return TaskResult::Complete;
2159 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
2160 bool GetName(nsACString& aName) override {
2161 aName.AssignLiteral("UpdateWorkspaceIdTask");
2162 return true;
2164 #endif
2166 RefPtr<nsWindow> mSelf;
2169 if (aDesktop.mUpdateIsQueued) {
2170 return;
2173 aDesktop.mUpdateIsQueued = true;
2174 TaskController::Get()->AddTask(MakeAndAddRef<UpdateWorkspaceIdTask>(this));
2177 void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
2178 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2179 if (!desktopManager) {
2180 return;
2183 GUID desktop;
2184 const nsString flat = PromiseFlatString(workspaceID);
2185 RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
2186 if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
2187 if (SUCCEEDED(desktopManager->MoveWindowToDesktop(mWnd, desktop))) {
2188 auto desktop = mDesktopId.Lock();
2189 desktop->mID = workspaceID;
2194 void nsWindow::SuppressAnimation(bool aSuppress) {
2195 DWORD dwAttribute = aSuppress ? TRUE : FALSE;
2196 DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
2197 sizeof dwAttribute);
2200 // Constrain a potential move to fit onscreen
2201 // Position (aX, aY) is specified in Windows screen (logical) pixels,
2202 // except when using per-monitor DPI, in which case it's device pixels.
2203 void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
2204 if (!mIsTopWidgetWindow) // only a problem for top-level windows
2205 return;
2207 double dpiScale = GetDesktopToDeviceScale().scale;
2209 // We need to use the window size in the kind of pixels used for window-
2210 // manipulation APIs.
2211 int32_t logWidth =
2212 std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
2213 int32_t logHeight =
2214 std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
2216 /* get our playing field. use the current screen, or failing that
2217 for any reason, use device caps for the default screen. */
2218 RECT screenRect;
2220 nsCOMPtr<nsIScreenManager> screenmgr =
2221 do_GetService(sScreenManagerContractID);
2222 if (!screenmgr) {
2223 return;
2225 nsCOMPtr<nsIScreen> screen;
2226 int32_t left, top, width, height;
2228 screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
2229 getter_AddRefs(screen));
2230 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
2231 // For normalized windows, use the desktop work area.
2232 nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
2233 if (NS_FAILED(rv)) {
2234 return;
2236 } else {
2237 // For full screen windows, use the desktop.
2238 nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height);
2239 if (NS_FAILED(rv)) {
2240 return;
2243 screenRect.left = left;
2244 screenRect.right = left + width;
2245 screenRect.top = top;
2246 screenRect.bottom = top + height;
2248 if (aPoint.x < screenRect.left)
2249 aPoint.x = screenRect.left;
2250 else if (aPoint.x >= screenRect.right - logWidth)
2251 aPoint.x = screenRect.right - logWidth;
2253 if (aPoint.y < screenRect.top)
2254 aPoint.y = screenRect.top;
2255 else if (aPoint.y >= screenRect.bottom - logHeight)
2256 aPoint.y = screenRect.bottom - logHeight;
2259 /**************************************************************
2261 * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
2263 * Enabling and disabling the widget.
2265 **************************************************************/
2267 // Enable/disable this component
2268 void nsWindow::Enable(bool bState) {
2269 if (mWnd) {
2270 ::EnableWindow(mWnd, bState);
2274 // Return the current enable state
2275 bool nsWindow::IsEnabled() const {
2276 return !mWnd || (::IsWindowEnabled(mWnd) &&
2277 ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
2280 /**************************************************************
2282 * SECTION: nsIWidget::SetFocus
2284 * Give the focus to this widget.
2286 **************************************************************/
2288 void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2289 if (mWnd) {
2290 #ifdef WINSTATE_DEBUG_OUTPUT
2291 if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
2292 MOZ_LOG(gWindowsLog, LogLevel::Info,
2293 ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
2294 } else {
2295 MOZ_LOG(gWindowsLog, LogLevel::Info,
2296 ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
2298 #endif
2299 // Uniconify, if necessary
2300 HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
2301 if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
2302 ::ShowWindow(toplevelWnd, SW_RESTORE);
2304 ::SetFocus(mWnd);
2308 /**************************************************************
2310 * SECTION: Bounds
2312 * GetBounds, GetClientBounds, GetScreenBounds,
2313 * GetRestoredBounds, GetClientOffset, SetNonClientMargins
2315 * Bound calculations.
2317 **************************************************************/
2319 // Return the window's full dimensions in screen coordinates.
2320 // If the window has a parent, converts the origin to an offset
2321 // of the parent's screen origin.
2322 LayoutDeviceIntRect nsWindow::GetBounds() {
2323 if (!mWnd) {
2324 return mBounds;
2327 RECT r;
2328 VERIFY(::GetWindowRect(mWnd, &r));
2330 LayoutDeviceIntRect rect;
2332 // assign size
2333 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2335 // popup window bounds' are in screen coordinates, not relative to parent
2336 // window
2337 if (mWindowType == WindowType::Popup) {
2338 rect.MoveTo(r.left, r.top);
2339 return rect;
2342 // chrome on parent:
2343 // ___ 5,5 (chrome start)
2344 // | ____ 10,10 (client start)
2345 // | | ____ 20,20 (child start)
2346 // | | |
2347 // 20,20 - 5,5 = 15,15 (??)
2348 // minus GetClientOffset:
2349 // 15,15 - 5,5 = 10,10
2351 // no chrome on parent:
2352 // ______ 10,10 (win start)
2353 // | ____ 20,20 (child start)
2354 // | |
2355 // 20,20 - 10,10 = 10,10
2357 // walking the chain:
2358 // ___ 5,5 (chrome start)
2359 // | ___ 10,10 (client start)
2360 // | | ___ 20,20 (child start)
2361 // | | | __ 30,30 (child start)
2362 // | | | |
2363 // 30,30 - 20,20 = 10,10 (offset from second child to first)
2364 // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
2365 // minus GetClientOffset:
2366 // 25,25 - 5,5 = 20,20 (offset from second child to parent client)
2368 // convert coordinates if parent exists
2369 HWND parent = ::GetParent(mWnd);
2370 if (parent) {
2371 RECT pr;
2372 VERIFY(::GetWindowRect(parent, &pr));
2373 r.left -= pr.left;
2374 r.top -= pr.top;
2375 // adjust for chrome
2376 nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
2377 if (pWidget && pWidget->IsTopLevelWidget()) {
2378 LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
2379 r.left -= clientOffset.x;
2380 r.top -= clientOffset.y;
2383 rect.MoveTo(r.left, r.top);
2384 if (mCompositorSession &&
2385 !wr::WindowSizeSanityCheck(rect.width, rect.height)) {
2386 gfxCriticalNoteOnce << "Invalid size" << rect << " size mode "
2387 << mFrameState->GetSizeMode();
2390 return rect;
2393 // Get this component dimension
2394 LayoutDeviceIntRect nsWindow::GetClientBounds() {
2395 if (!mWnd) {
2396 return LayoutDeviceIntRect(0, 0, 0, 0);
2399 RECT r;
2400 if (!::GetClientRect(mWnd, &r)) {
2401 MOZ_ASSERT_UNREACHABLE("unexpected to be called");
2402 gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
2403 return mBounds;
2406 LayoutDeviceIntRect bounds = GetBounds();
2407 LayoutDeviceIntRect rect;
2408 rect.MoveTo(bounds.TopLeft() + GetClientOffset());
2409 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2410 return rect;
2413 // Like GetBounds, but don't offset by the parent
2414 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
2415 if (!mWnd) {
2416 return mBounds;
2419 RECT r;
2420 VERIFY(::GetWindowRect(mWnd, &r));
2422 return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2425 nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
2426 if (SizeMode() == nsSizeMode_Normal) {
2427 aRect = GetScreenBounds();
2428 return NS_OK;
2430 if (!mWnd) {
2431 return NS_ERROR_FAILURE;
2434 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2435 VERIFY(::GetWindowPlacement(mWnd, &pl));
2436 const RECT& r = pl.rcNormalPosition;
2438 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2439 if (!monitor) {
2440 return NS_ERROR_FAILURE;
2442 MONITORINFO mi = {sizeof(MONITORINFO)};
2443 VERIFY(::GetMonitorInfo(monitor, &mi));
2445 aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2446 aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
2447 mi.rcWork.top - mi.rcMonitor.top);
2448 return NS_OK;
2451 // Return the x,y offset of the client area from the origin of the window. If
2452 // the window is borderless returns (0,0).
2453 LayoutDeviceIntPoint nsWindow::GetClientOffset() {
2454 if (!mWnd) {
2455 return LayoutDeviceIntPoint(0, 0);
2458 RECT r1;
2459 GetWindowRect(mWnd, &r1);
2460 LayoutDeviceIntPoint pt = WidgetToScreenOffset();
2461 return LayoutDeviceIntPoint(pt.x - LayoutDeviceIntCoord(r1.left),
2462 pt.y - LayoutDeviceIntCoord(r1.top));
2465 void nsWindow::ResetLayout() {
2466 // This will trigger a frame changed event, triggering
2467 // nc calc size and a sizemode gecko event.
2468 SetWindowPos(mWnd, 0, 0, 0, 0, 0,
2469 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
2470 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
2472 // If hidden, just send the frame changed event for now.
2473 if (!mIsVisible) {
2474 return;
2477 // Send a gecko size event to trigger reflow.
2478 RECT clientRc = {0};
2479 GetClientRect(mWnd, &clientRc);
2480 OnResize(WinUtils::ToIntRect(clientRc).Size());
2482 // Invalidate and update
2483 Invalidate();
2486 // Internally track the caption status via a window property. Required
2487 // due to our internal handling of WM_NCACTIVATE when custom client
2488 // margins are set.
2489 static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty";
2490 typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
2491 static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr>
2492 sGetWindowInfoPtrStub;
2494 BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) {
2495 if (!sGetWindowInfoPtrStub) {
2496 NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!");
2497 return FALSE;
2499 int windowStatus =
2500 reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty));
2501 // No property set, return the default data.
2502 if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi);
2503 // Call GetWindowInfo and update dwWindowStatus with our
2504 // internally tracked value.
2505 BOOL result = sGetWindowInfoPtrStub(hWnd, pwi);
2506 if (result && pwi)
2507 pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION);
2508 return result;
2511 void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) {
2512 if (!mWnd) return;
2514 sUser32Intercept.Init("user32.dll");
2515 sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo",
2516 &GetWindowInfoHook);
2517 if (!sGetWindowInfoPtrStub) {
2518 return;
2521 // Update our internally tracked caption status
2522 SetPropW(mWnd, kManageWindowInfoProperty,
2523 reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
2526 #define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
2527 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
2529 void nsWindow::UpdateDarkModeToolbar() {
2530 PreferenceSheet::EnsureInitialized();
2531 BOOL dark = PreferenceSheet::ColorSchemeForChrome() == ColorScheme::Dark;
2532 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &dark,
2533 sizeof dark);
2534 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
2535 sizeof dark);
2538 LayoutDeviceIntMargin nsWindow::NormalWindowNonClientOffset() const {
2539 LayoutDeviceIntMargin nonClientOffset;
2541 // We're dealing with a "normal" window (not maximized, minimized, or
2542 // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset`
2543 // accordingly.
2545 // Setting `mNonClientOffset` to 0 has the effect of leaving the default
2546 // frame intact. Setting it to a value greater than 0 reduces the frame
2547 // size by that amount.
2549 if (mNonClientMargins.top > 0) {
2550 nonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
2551 } else if (mNonClientMargins.top == 0) {
2552 nonClientOffset.top = mCaptionHeight;
2553 } else {
2554 nonClientOffset.top = 0;
2557 if (mNonClientMargins.bottom > 0) {
2558 nonClientOffset.bottom =
2559 std::min(mVertResizeMargin, mNonClientMargins.bottom);
2560 } else if (mNonClientMargins.bottom == 0) {
2561 nonClientOffset.bottom = mVertResizeMargin;
2562 } else {
2563 nonClientOffset.bottom = 0;
2566 if (mNonClientMargins.left > 0) {
2567 nonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left);
2568 } else if (mNonClientMargins.left == 0) {
2569 nonClientOffset.left = mHorResizeMargin;
2570 } else {
2571 nonClientOffset.left = 0;
2574 if (mNonClientMargins.right > 0) {
2575 nonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right);
2576 } else if (mNonClientMargins.right == 0) {
2577 nonClientOffset.right = mHorResizeMargin;
2578 } else {
2579 nonClientOffset.right = 0;
2581 return nonClientOffset;
2585 * Called when the window layout changes: full screen mode transitions,
2586 * theme changes, and composition changes. Calculates the new non-client
2587 * margins and fires off a frame changed event, which triggers an nc calc
2588 * size windows event, kicking the changes in.
2590 * The offsets calculated here are based on the value of `mNonClientMargins`
2591 * which is specified in the "chromemargins" attribute of the window. For
2592 * each margin, the value specified has the following meaning:
2593 * -1 - leave the default frame in place
2594 * 0 - remove the frame
2595 * >0 - frame size equals min(0, (default frame size - margin value))
2597 * This function calculates and populates `mNonClientOffset`.
2598 * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
2599 * as (default frame size - offset). For example, if the left frame should
2600 * be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
2601 * will equal 1.
2603 * For maximized, fullscreen, and minimized windows, the values stored in
2604 * `mNonClientMargins` are ignored, and special processing takes place.
2606 * For non-glass windows, we only allow frames to be their default size
2607 * or removed entirely.
2609 bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) {
2610 if (!mCustomNonClient) {
2611 return false;
2614 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
2616 bool hasCaption =
2617 bool(mBorderStyle & (BorderStyle::All | BorderStyle::Title |
2618 BorderStyle::Menu | BorderStyle::Default));
2620 float dpi = GetDPI();
2622 // mCaptionHeight is the default size of the NC area at
2623 // the top of the window. If the window has a caption,
2624 // the size is calculated as the sum of:
2625 // SM_CYFRAME - The thickness of the sizing border
2626 // around a resizable window
2627 // SM_CXPADDEDBORDER - The amount of border padding
2628 // for captioned windows
2629 // SM_CYCAPTION - The height of the caption area
2631 // If the window does not have a caption, mCaptionHeight will be equal to
2632 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2633 mCaptionHeight =
2634 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2635 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +
2636 WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2637 : 0);
2638 if (!mUseResizeMarginOverrides) {
2639 // mHorResizeMargin is the size of the default NC areas on the
2640 // left and right sides of our window. It is calculated as
2641 // the sum of:
2642 // SM_CXFRAME - The thickness of the sizing border
2643 // SM_CXPADDEDBORDER - The amount of border padding
2644 // for captioned windows
2646 // If the window does not have a caption, mHorResizeMargin will be equal to
2647 // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
2648 mHorResizeMargin =
2649 WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
2650 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2651 : 0);
2653 // mVertResizeMargin is the size of the default NC area at the
2654 // bottom of the window. It is calculated as the sum of:
2655 // SM_CYFRAME - The thickness of the sizing border
2656 // SM_CXPADDEDBORDER - The amount of border padding
2657 // for captioned windows.
2659 // If the window does not have a caption, mVertResizeMargin will be equal to
2660 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2661 mVertResizeMargin =
2662 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2663 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2664 : 0);
2667 if (sizeMode == nsSizeMode_Minimized) {
2668 // Use default frame size for minimized windows
2669 mNonClientOffset.top = 0;
2670 mNonClientOffset.left = 0;
2671 mNonClientOffset.right = 0;
2672 mNonClientOffset.bottom = 0;
2673 } else if (sizeMode == nsSizeMode_Fullscreen) {
2674 // Remove the default frame from the top of our fullscreen window. This
2675 // makes the whole caption part of our client area, allowing us to draw
2676 // in the whole caption area. Additionally remove the default frame from
2677 // the left, right, and bottom.
2678 mNonClientOffset.top = mCaptionHeight;
2679 mNonClientOffset.bottom = mVertResizeMargin;
2680 mNonClientOffset.left = mHorResizeMargin;
2681 mNonClientOffset.right = mHorResizeMargin;
2682 } else if (sizeMode == nsSizeMode_Maximized) {
2683 // We make the entire frame part of the client area. We leave the default
2684 // frame sizes for left, right and bottom since Windows will automagically
2685 // position the edges "offscreen" for maximized windows.
2686 int verticalResize =
2687 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2688 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2689 : 0);
2691 mNonClientOffset.top = mCaptionHeight - verticalResize;
2692 mNonClientOffset.bottom = 0;
2693 mNonClientOffset.left = 0;
2694 mNonClientOffset.right = 0;
2696 mozilla::Maybe<UINT> maybeEdge = GetHiddenTaskbarEdge();
2697 if (maybeEdge) {
2698 auto edge = maybeEdge.value();
2699 if (ABE_LEFT == edge) {
2700 mNonClientOffset.left -= kHiddenTaskbarSize;
2701 } else if (ABE_RIGHT == edge) {
2702 mNonClientOffset.right -= kHiddenTaskbarSize;
2703 } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
2704 mNonClientOffset.bottom -= kHiddenTaskbarSize;
2707 // When we are drawing the non-client region, we need
2708 // to clear the portion of the NC region that is exposed by the
2709 // hidden taskbar. As above, we clear the bottom of the NC region
2710 // when the taskbar is at the top of the screen.
2711 UINT clearEdge = (edge == ABE_TOP) ? ABE_BOTTOM : edge;
2712 mClearNCEdge = Some(clearEdge);
2714 } else {
2715 mNonClientOffset = NormalWindowNonClientOffset();
2718 if (aReflowWindow) {
2719 // Force a reflow of content based on the new client
2720 // dimensions.
2721 ResetLayout();
2724 return true;
2727 nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) {
2728 if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None)
2729 return NS_ERROR_INVALID_ARG;
2731 if (mHideChrome) {
2732 mFutureMarginsOnceChromeShows = margins;
2733 mFutureMarginsToUse = true;
2734 return NS_OK;
2736 mFutureMarginsToUse = false;
2738 // Request for a reset
2739 if (margins.top == -1 && margins.left == -1 && margins.right == -1 &&
2740 margins.bottom == -1) {
2741 mCustomNonClient = false;
2742 mNonClientMargins = margins;
2743 // Force a reflow of content based on the new client
2744 // dimensions.
2745 ResetLayout();
2747 int windowStatus =
2748 reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty));
2749 if (windowStatus) {
2750 ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0);
2753 return NS_OK;
2756 if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 ||
2757 margins.right < -1)
2758 return NS_ERROR_INVALID_ARG;
2760 mNonClientMargins = margins;
2761 mCustomNonClient = true;
2762 if (!UpdateNonClientMargins()) {
2763 NS_WARNING("UpdateNonClientMargins failed!");
2764 return NS_OK;
2767 return NS_OK;
2770 void nsWindow::SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) {
2771 mUseResizeMarginOverrides = true;
2772 mHorResizeMargin = aResizeMargin;
2773 mVertResizeMargin = aResizeMargin;
2774 UpdateNonClientMargins();
2777 void nsWindow::InvalidateNonClientRegion() {
2778 // +-+-----------------------+-+
2779 // | | app non-client chrome | |
2780 // | +-----------------------+ |
2781 // | | app client chrome | | }
2782 // | +-----------------------+ | }
2783 // | | app content | | } area we don't want to invalidate
2784 // | +-----------------------+ | }
2785 // | | app client chrome | | }
2786 // | +-----------------------+ |
2787 // +---------------------------+ <
2788 // ^ ^ windows non-client chrome
2789 // client area = app *
2790 RECT rect;
2791 GetWindowRect(mWnd, &rect);
2792 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2793 HRGN winRgn = CreateRectRgnIndirect(&rect);
2795 // Subtract app client chrome and app content leaving
2796 // windows non-client chrome and app non-client chrome
2797 // in winRgn.
2798 GetWindowRect(mWnd, &rect);
2799 rect.top += mCaptionHeight;
2800 rect.right -= mHorResizeMargin;
2801 rect.bottom -= mVertResizeMargin;
2802 rect.left += mHorResizeMargin;
2803 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2804 HRGN clientRgn = CreateRectRgnIndirect(&rect);
2805 CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF);
2806 DeleteObject(clientRgn);
2808 // triggers ncpaint and paint events for the two areas
2809 RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE);
2810 DeleteObject(winRgn);
2813 /**************************************************************
2815 * SECTION: nsIWidget::SetBackgroundColor
2817 * Sets the window background paint color.
2819 **************************************************************/
2821 void nsWindow::SetBackgroundColor(const nscolor& aColor) {
2822 if (mBrush) ::DeleteObject(mBrush);
2824 mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor));
2825 if (mWnd != nullptr) {
2826 ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush);
2830 /**************************************************************
2832 * SECTION: nsIWidget::SetCursor
2834 * SetCursor and related utilities for manging cursor state.
2836 **************************************************************/
2838 // Set this component cursor
2839 static HCURSOR CursorFor(nsCursor aCursor) {
2840 switch (aCursor) {
2841 case eCursor_select:
2842 return ::LoadCursor(nullptr, IDC_IBEAM);
2843 case eCursor_wait:
2844 return ::LoadCursor(nullptr, IDC_WAIT);
2845 case eCursor_hyperlink:
2846 return ::LoadCursor(nullptr, IDC_HAND);
2847 case eCursor_standard:
2848 case eCursor_context_menu: // XXX See bug 258960.
2849 return ::LoadCursor(nullptr, IDC_ARROW);
2851 case eCursor_n_resize:
2852 case eCursor_s_resize:
2853 return ::LoadCursor(nullptr, IDC_SIZENS);
2855 case eCursor_w_resize:
2856 case eCursor_e_resize:
2857 return ::LoadCursor(nullptr, IDC_SIZEWE);
2859 case eCursor_nw_resize:
2860 case eCursor_se_resize:
2861 return ::LoadCursor(nullptr, IDC_SIZENWSE);
2863 case eCursor_ne_resize:
2864 case eCursor_sw_resize:
2865 return ::LoadCursor(nullptr, IDC_SIZENESW);
2867 case eCursor_crosshair:
2868 return ::LoadCursor(nullptr, IDC_CROSS);
2870 case eCursor_move:
2871 return ::LoadCursor(nullptr, IDC_SIZEALL);
2873 case eCursor_help:
2874 return ::LoadCursor(nullptr, IDC_HELP);
2876 case eCursor_copy: // CSS3
2877 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
2879 case eCursor_alias:
2880 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
2882 case eCursor_cell:
2883 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
2884 case eCursor_grab:
2885 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
2887 case eCursor_grabbing:
2888 return ::LoadCursor(nsToolkit::mDllInstance,
2889 MAKEINTRESOURCE(IDC_GRABBING));
2891 case eCursor_spinning:
2892 return ::LoadCursor(nullptr, IDC_APPSTARTING);
2894 case eCursor_zoom_in:
2895 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
2897 case eCursor_zoom_out:
2898 return ::LoadCursor(nsToolkit::mDllInstance,
2899 MAKEINTRESOURCE(IDC_ZOOMOUT));
2901 case eCursor_not_allowed:
2902 case eCursor_no_drop:
2903 return ::LoadCursor(nullptr, IDC_NO);
2905 case eCursor_col_resize:
2906 return ::LoadCursor(nsToolkit::mDllInstance,
2907 MAKEINTRESOURCE(IDC_COLRESIZE));
2909 case eCursor_row_resize:
2910 return ::LoadCursor(nsToolkit::mDllInstance,
2911 MAKEINTRESOURCE(IDC_ROWRESIZE));
2913 case eCursor_vertical_text:
2914 return ::LoadCursor(nsToolkit::mDllInstance,
2915 MAKEINTRESOURCE(IDC_VERTICALTEXT));
2917 case eCursor_all_scroll:
2918 // XXX not 100% appropriate perhaps
2919 return ::LoadCursor(nullptr, IDC_SIZEALL);
2921 case eCursor_nesw_resize:
2922 return ::LoadCursor(nullptr, IDC_SIZENESW);
2924 case eCursor_nwse_resize:
2925 return ::LoadCursor(nullptr, IDC_SIZENWSE);
2927 case eCursor_ns_resize:
2928 return ::LoadCursor(nullptr, IDC_SIZENS);
2930 case eCursor_ew_resize:
2931 return ::LoadCursor(nullptr, IDC_SIZEWE);
2933 case eCursor_none:
2934 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
2936 default:
2937 NS_ERROR("Invalid cursor type");
2938 return nullptr;
2942 static HCURSOR CursorForImage(const nsIWidget::Cursor& aCursor,
2943 CSSToLayoutDeviceScale aScale) {
2944 if (!aCursor.IsCustom()) {
2945 return nullptr;
2948 nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
2950 // Reject cursors greater than 128 pixels in either direction, to prevent
2951 // spoofing.
2952 // XXX ideally we should rescale. Also, we could modify the API to
2953 // allow trusted content to set larger cursors.
2954 if (size.width > 128 || size.height > 128) {
2955 return nullptr;
2958 LayoutDeviceIntSize layoutSize =
2959 RoundedToInt(CSSIntSize(size.width, size.height) * aScale);
2960 LayoutDeviceIntPoint hotspot =
2961 RoundedToInt(CSSIntPoint(aCursor.mHotspotX, aCursor.mHotspotY) * aScale);
2962 HCURSOR cursor;
2963 nsresult rv = nsWindowGfx::CreateIcon(aCursor.mContainer, true, hotspot,
2964 layoutSize, &cursor);
2965 if (NS_FAILED(rv)) {
2966 return nullptr;
2969 return cursor;
2972 void nsWindow::SetCursor(const Cursor& aCursor) {
2973 static HCURSOR sCurrentHCursor = nullptr;
2974 static bool sCurrentHCursorIsCustom = false;
2976 mCursor = aCursor;
2978 if (sCurrentCursor == aCursor && sCurrentHCursor && !mUpdateCursor) {
2979 // Cursors in windows are global, so even if our mUpdateCursor flag is
2980 // false we always need to make sure the Windows cursor is up-to-date,
2981 // since stuff like native drag and drop / resizers code can mutate it
2982 // outside of this method.
2983 ::SetCursor(sCurrentHCursor);
2984 return;
2987 mUpdateCursor = false;
2989 if (sCurrentHCursorIsCustom) {
2990 ::DestroyIcon(sCurrentHCursor);
2992 sCurrentHCursor = nullptr;
2993 sCurrentHCursorIsCustom = false;
2994 sCurrentCursor = aCursor;
2996 HCURSOR cursor = nullptr;
2997 if (mCustomCursorAllowed) {
2998 cursor = CursorForImage(aCursor, GetDefaultScale());
3000 bool custom = false;
3001 if (cursor) {
3002 custom = true;
3003 } else {
3004 cursor = CursorFor(aCursor.mDefaultCursor);
3007 if (!cursor) {
3008 return;
3011 sCurrentHCursor = cursor;
3012 sCurrentHCursorIsCustom = custom;
3013 ::SetCursor(cursor);
3016 /**************************************************************
3018 * SECTION: nsIWidget::Get/SetTransparencyMode
3020 * Manage the transparency mode of the window containing this
3021 * widget. Only works for popup and dialog windows when the
3022 * Desktop Window Manager compositor is not enabled.
3024 **************************************************************/
3026 TransparencyMode nsWindow::GetTransparencyMode() {
3027 return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
3030 void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
3031 nsWindow* window = GetTopLevelWindow(true);
3032 MOZ_ASSERT(window);
3034 if (!window || window->DestroyCalled()) {
3035 return;
3038 window->SetWindowTranslucencyInner(aMode);
3041 /**************************************************************
3043 * SECTION: nsIWidget::UpdateWindowDraggingRegion
3045 * For setting the draggable titlebar region from CSS
3046 * with -moz-window-dragging: drag.
3048 **************************************************************/
3050 void nsWindow::UpdateWindowDraggingRegion(
3051 const LayoutDeviceIntRegion& aRegion) {
3052 mDraggableRegion = aRegion;
3055 /**************************************************************
3057 * SECTION: nsIWidget::HideWindowChrome
3059 * Show or hide window chrome.
3061 **************************************************************/
3063 void nsWindow::HideWindowChrome(bool aShouldHide) {
3064 HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
3065 if (!WinUtils::GetNSWindowPtr(hwnd)) {
3066 NS_WARNING("Trying to hide window decorations in an embedded context");
3067 return;
3070 if (mHideChrome == aShouldHide) return;
3072 DWORD_PTR style, exStyle;
3073 mHideChrome = aShouldHide;
3074 if (aShouldHide) {
3075 DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
3076 DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
3078 style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME);
3079 exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
3080 WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
3082 mOldStyle = tempStyle;
3083 mOldExStyle = tempExStyle;
3084 } else {
3085 if (!mOldStyle || !mOldExStyle) {
3086 mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
3087 mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
3090 style = mOldStyle;
3091 exStyle = mOldExStyle;
3092 if (mFutureMarginsToUse) {
3093 SetNonClientMargins(mFutureMarginsOnceChromeShows);
3097 VERIFY_WINDOW_STYLE(style);
3098 ::SetWindowLongPtrW(hwnd, GWL_STYLE, style);
3099 ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle);
3102 /**************************************************************
3104 * SECTION: nsWindow::Invalidate
3106 * Invalidate an area of the client for painting.
3108 **************************************************************/
3110 // Invalidate this component visible area
3111 void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
3112 bool aIncludeChildren) {
3113 if (!mWnd) {
3114 return;
3117 #ifdef WIDGET_DEBUG_OUTPUT
3118 debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
3119 #endif // WIDGET_DEBUG_OUTPUT
3121 DWORD flags = RDW_INVALIDATE;
3122 if (aEraseBackground) {
3123 flags |= RDW_ERASE;
3125 if (aUpdateNCArea) {
3126 flags |= RDW_FRAME;
3128 if (aIncludeChildren) {
3129 flags |= RDW_ALLCHILDREN;
3132 VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
3135 // Invalidate this component visible area
3136 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
3137 if (mWnd) {
3138 #ifdef WIDGET_DEBUG_OUTPUT
3139 debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
3140 #endif // WIDGET_DEBUG_OUTPUT
3142 RECT rect;
3144 rect.left = aRect.X();
3145 rect.top = aRect.Y();
3146 rect.right = aRect.XMost();
3147 rect.bottom = aRect.YMost();
3149 VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
3153 static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
3154 WPARAM wParam,
3155 LPARAM lParam) {
3156 switch (uMsg) {
3157 case WM_FULLSCREEN_TRANSITION_BEFORE:
3158 case WM_FULLSCREEN_TRANSITION_AFTER: {
3159 DWORD duration = (DWORD)lParam;
3160 DWORD flags = AW_BLEND;
3161 if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
3162 flags |= AW_HIDE;
3164 ::AnimateWindow(hWnd, duration, flags);
3165 // The message sender should have added ref for us.
3166 NS_DispatchToMainThread(
3167 already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
3168 break;
3170 case WM_DESTROY:
3171 ::PostQuitMessage(0);
3172 break;
3173 default:
3174 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
3176 return 0;
3179 struct FullscreenTransitionInitData {
3180 LayoutDeviceIntRect mBounds;
3181 HANDLE mSemaphore;
3182 HANDLE mThread;
3183 HWND mWnd;
3185 FullscreenTransitionInitData()
3186 : mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
3188 ~FullscreenTransitionInitData() {
3189 if (mSemaphore) {
3190 ::CloseHandle(mSemaphore);
3192 if (mThread) {
3193 ::CloseHandle(mThread);
3198 static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
3199 // Initialize window class
3200 static bool sInitialized = false;
3201 if (!sInitialized) {
3202 WNDCLASSW wc = {};
3203 wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
3204 wc.hInstance = nsToolkit::mDllInstance;
3205 wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
3206 wc.lpszClassName = kClassNameTransition;
3207 ::RegisterClassW(&wc);
3208 sInitialized = true;
3211 auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
3212 HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
3213 nullptr, nsToolkit::mDllInstance, nullptr);
3214 if (!wnd) {
3215 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3216 return 0;
3219 // Since AnimateWindow blocks the thread of the transition window,
3220 // we need to hide the cursor for that window, otherwise the system
3221 // would show the busy pointer to the user.
3222 ::ShowCursor(false);
3223 ::SetWindowLongW(wnd, GWL_STYLE, 0);
3224 ::SetWindowLongW(
3225 wnd, GWL_EXSTYLE,
3226 WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
3227 ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
3228 data->mBounds.Width(), data->mBounds.Height(), 0);
3229 data->mWnd = wnd;
3230 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3231 // The initialization data may no longer be valid
3232 // after we release the semaphore.
3233 data = nullptr;
3235 MSG msg;
3236 while (::GetMessageW(&msg, nullptr, 0, 0)) {
3237 ::TranslateMessage(&msg);
3238 ::DispatchMessage(&msg);
3240 ::ShowCursor(true);
3241 ::DestroyWindow(wnd);
3242 return 0;
3245 class FullscreenTransitionData final : public nsISupports {
3246 public:
3247 NS_DECL_ISUPPORTS
3249 explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
3250 MOZ_ASSERT(NS_IsMainThread(),
3251 "FullscreenTransitionData "
3252 "should be constructed in the main thread");
3255 const HWND mWnd;
3257 private:
3258 ~FullscreenTransitionData() {
3259 MOZ_ASSERT(NS_IsMainThread(),
3260 "FullscreenTransitionData "
3261 "should be deconstructed in the main thread");
3262 ::PostMessageW(mWnd, WM_DESTROY, 0, 0);
3266 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
3268 /* virtual */
3269 bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
3270 FullscreenTransitionInitData initData;
3271 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
3272 const DesktopIntRect rect = screen->GetRectDisplayPix();
3273 MOZ_ASSERT(BoundsUseDesktopPixels(),
3274 "Should only be called on top-level window");
3275 initData.mBounds =
3276 LayoutDeviceIntRect::Round(rect * GetDesktopToDeviceScale());
3278 // Create a semaphore for synchronizing the window handle which will
3279 // be created by the transition thread and used by the main thread for
3280 // posting the transition messages.
3281 initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
3282 if (initData.mSemaphore) {
3283 initData.mThread = ::CreateThread(
3284 nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
3285 if (initData.mThread) {
3286 ::WaitForSingleObject(initData.mSemaphore, INFINITE);
3289 if (!initData.mWnd) {
3290 return false;
3293 mTransitionWnd = initData.mWnd;
3295 auto data = new FullscreenTransitionData(initData.mWnd);
3296 *aData = data;
3297 NS_ADDREF(data);
3298 return true;
3301 /* virtual */
3302 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
3303 uint16_t aDuration,
3304 nsISupports* aData,
3305 nsIRunnable* aCallback) {
3306 auto data = static_cast<FullscreenTransitionData*>(aData);
3307 nsCOMPtr<nsIRunnable> callback = aCallback;
3308 UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
3309 : WM_FULLSCREEN_TRANSITION_AFTER;
3310 WPARAM wparam = (WPARAM)callback.forget().take();
3311 ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
3314 /* virtual */
3315 void nsWindow::CleanupFullscreenTransition() {
3316 MOZ_ASSERT(NS_IsMainThread(),
3317 "CleanupFullscreenTransition "
3318 "should only run on the main thread");
3320 mTransitionWnd = nullptr;
3323 void nsWindow::TryDwmResizeHack() {
3324 // The "DWM resize hack", aka the "fullscreen resize hack", is a workaround
3325 // for DWM's occasional and not-entirely-predictable failure to update its
3326 // internal state when the client area of a window changes without changing
3327 // the window size. The effect of this is that DWM will clip the content of
3328 // the window to its former client area.
3330 // It is not known under what circumstances the bug will trigger. Windows 11
3331 // is known to be required, but many Windows 11 machines do not exhibit the
3332 // issue. Even machines that _do_ exhibit it will sometimes not do so when
3333 // apparently-irrelevant changes are made to the configuration. (See bug
3334 // 1763981.)
3336 // The bug is triggered by Firefox when a maximized window (which has window
3337 // decorations) becomes fullscreen (which doesn't). To work around this, if we
3338 // think it may occur, we "flicker-resize" the relevant window -- that is, we
3339 // reduce its height by 1px, then restore it. This causes DWM to acquire the
3340 // new client-area metrics.
3342 // Note that, in particular, this bug will not occur when using a separate
3343 // compositor window, as our compositor windows never have any nonclient area.
3345 // This is admittedly a sledgehammer where a screwdriver should suffice.
3347 // ---------------------------------------------------------------------------
3349 // Regardless of preferences or heuristics, only apply the hack if this is the
3350 // first time we've entered fullscreen across the entire Firefox session.
3351 // (Subsequent transitions to fullscreen, even with different windows, don't
3352 // appear to induce the bug.)
3354 // (main thread only; `atomic` not needed)
3355 static bool sIsFirstFullscreenEntry = true;
3356 bool isFirstFullscreenEntry = sIsFirstFullscreenEntry;
3357 sIsFirstFullscreenEntry = false;
3358 if (MOZ_LIKELY(!isFirstFullscreenEntry)) {
3359 return;
3361 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3362 ("%s: first fullscreen entry", __PRETTY_FUNCTION__));
3365 // Check whether to try to apply the DWM resize hack, based on the override
3366 // pref and/or some internal heuristics.
3368 const auto hackApplicationHeuristics = [&]() -> bool {
3369 // The bug has only been seen under Windows 11. (At time of writing, this
3370 // is the latest version of Windows.)
3371 if (!IsWin11OrLater()) {
3372 return false;
3375 KnowsCompositor const* const kc = mWindowRenderer->AsKnowsCompositor();
3376 // This should never happen...
3377 MOZ_ASSERT(kc);
3378 // ... so if it does, we are in uncharted territory: don't apply the hack.
3379 if (!kc) {
3380 return false;
3383 // The bug doesn't occur when we're using a separate compositor window
3384 // (since the compositor window always comprises exactly its client area,
3385 // with no non-client border).
3386 if (kc->GetUseCompositorWnd()) {
3387 return false;
3390 // Otherwise, apply the hack.
3391 return true;
3394 // Figure out whether or not we should perform the hack, and -- arguably
3395 // more importantly -- log that decision.
3396 bool const shouldApplyHack = [&]() {
3397 enum Reason : bool { Pref, Heuristics };
3398 auto const msg = [&](bool decision, Reason reason) -> bool {
3399 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3400 ("%s %s per %s", decision ? "applying" : "skipping",
3401 "DWM resize hack", reason == Pref ? "pref" : "heuristics"));
3402 return decision;
3404 switch (StaticPrefs::widget_windows_apply_dwm_resize_hack()) {
3405 case 0:
3406 return msg(false, Pref);
3407 case 1:
3408 return msg(true, Pref);
3409 default: // treat all other values as `auto`
3410 return msg(hackApplicationHeuristics(), Heuristics);
3412 }();
3414 if (!shouldApplyHack) {
3415 return;
3419 // The DWM bug is believed to involve a race condition: some users have
3420 // reported that setting a custom theme or adding unused command-line
3421 // parameters sometimes causes the bug to vanish.
3423 // Out of an abundance of caution, we therefore apply the hack in a later
3424 // event, rather than inline.
3425 NS_DispatchToMainThread(NS_NewRunnableFunction(
3426 "nsWindow::TryFullscreenResizeHack", [self = RefPtr(this)]() {
3427 HWND const hwnd = self->GetWindowHandle();
3429 if (self->mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
3430 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info,
3431 ("DWM resize hack: window no longer fullscreen; aborting"));
3432 return;
3435 RECT origRect;
3436 if (!::GetWindowRect(hwnd, &origRect)) {
3437 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Error,
3438 ("DWM resize hack: could not get window size?!"));
3439 return;
3441 LONG const x = origRect.left;
3442 LONG const y = origRect.top;
3443 LONG const width = origRect.right - origRect.left;
3444 LONG const height = origRect.bottom - origRect.top;
3446 MOZ_DIAGNOSTIC_ASSERT(!self->mIsPerformingDwmFlushHack);
3447 auto const onExit =
3448 MakeScopeExit([&, oldVal = self->mIsPerformingDwmFlushHack]() {
3449 self->mIsPerformingDwmFlushHack = oldVal;
3451 self->mIsPerformingDwmFlushHack = true;
3453 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3454 ("beginning DWM resize hack for HWND %08" PRIXPTR,
3455 uintptr_t(hwnd)));
3456 ::MoveWindow(hwnd, x, y, width, height - 1, FALSE);
3457 ::MoveWindow(hwnd, x, y, width, height, TRUE);
3458 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3459 ("concluded DWM resize hack for HWND %08" PRIXPTR,
3460 uintptr_t(hwnd)));
3461 }));
3464 void nsWindow::OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen) {
3465 MOZ_ASSERT((aOldSizeMode != nsSizeMode_Fullscreen) == aFullScreen);
3467 // HACK: Potentially flicker-resize the window, to force DWM to get the right
3468 // client-area information.
3469 if (aFullScreen) {
3470 TryDwmResizeHack();
3473 // Hide chrome and reposition window. Note this will also cache dimensions for
3474 // restoration, so it should only be called once per fullscreen request.
3476 // Don't do this when minimized, since our bounds make no sense then, nor when
3477 // coming back from that state.
3478 const bool toOrFromMinimized =
3479 mFrameState->GetSizeMode() == nsSizeMode_Minimized ||
3480 aOldSizeMode == nsSizeMode_Minimized;
3481 if (!toOrFromMinimized) {
3482 InfallibleMakeFullScreen(aFullScreen);
3485 // Possibly notify the taskbar that we have changed our fullscreen mode.
3486 TaskbarConcealer::OnFullscreenChanged(this, aFullScreen);
3489 nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
3490 mFrameState->EnsureFullscreenMode(aFullScreen);
3491 return NS_OK;
3494 /**************************************************************
3496 * SECTION: Native data storage
3498 * nsIWidget::GetNativeData
3499 * nsIWidget::FreeNativeData
3501 * Set or clear native data based on a constant.
3503 **************************************************************/
3505 // Return some native data according to aDataType
3506 void* nsWindow::GetNativeData(uint32_t aDataType) {
3507 switch (aDataType) {
3508 case NS_NATIVE_WIDGET:
3509 case NS_NATIVE_WINDOW:
3510 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
3511 return (void*)mWnd;
3512 case NS_NATIVE_GRAPHIC:
3513 MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
3514 return nullptr;
3515 case NS_RAW_NATIVE_IME_CONTEXT: {
3516 void* pseudoIMEContext = GetPseudoIMEContext();
3517 if (pseudoIMEContext) {
3518 return pseudoIMEContext;
3520 [[fallthrough]];
3522 case NS_NATIVE_TSF_THREAD_MGR:
3523 case NS_NATIVE_TSF_CATEGORY_MGR:
3524 case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
3525 return IMEHandler::GetNativeData(this, aDataType);
3527 default:
3528 break;
3531 return nullptr;
3534 // Free some native data according to aDataType
3535 void nsWindow::FreeNativeData(void* data, uint32_t aDataType) {
3536 switch (aDataType) {
3537 case NS_NATIVE_GRAPHIC:
3538 case NS_NATIVE_WIDGET:
3539 case NS_NATIVE_WINDOW:
3540 break;
3541 default:
3542 break;
3546 /**************************************************************
3548 * SECTION: nsIWidget::SetTitle
3550 * Set the main windows title text.
3552 **************************************************************/
3554 nsresult nsWindow::SetTitle(const nsAString& aTitle) {
3555 const nsString& strTitle = PromiseFlatString(aTitle);
3556 AutoRestore<bool> sendingText(mSendingSetText);
3557 mSendingSetText = true;
3558 ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
3559 return NS_OK;
3562 /**************************************************************
3564 * SECTION: nsIWidget::SetIcon
3566 * Set the main windows icon.
3568 **************************************************************/
3570 void nsWindow::SetBigIcon(HICON aIcon) {
3571 HICON icon =
3572 (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
3573 if (icon) {
3574 ::DestroyIcon(icon);
3577 mIconBig = aIcon;
3580 void nsWindow::SetSmallIcon(HICON aIcon) {
3581 HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
3582 (LPARAM)aIcon);
3583 if (icon) {
3584 ::DestroyIcon(icon);
3587 mIconSmall = aIcon;
3590 void nsWindow::SetIcon(const nsAString& aIconSpec) {
3591 // Assume the given string is a local identifier for an icon file.
3593 nsCOMPtr<nsIFile> iconFile;
3594 ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
3595 if (!iconFile) return;
3597 nsAutoString iconPath;
3598 iconFile->GetPath(iconPath);
3600 // XXX this should use MZLU (see bug 239279)
3602 ::SetLastError(0);
3604 HICON bigIcon =
3605 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3606 ::GetSystemMetrics(SM_CXICON),
3607 ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
3608 HICON smallIcon =
3609 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3610 ::GetSystemMetrics(SM_CXSMICON),
3611 ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
3613 if (bigIcon) {
3614 SetBigIcon(bigIcon);
3616 #ifdef DEBUG_SetIcon
3617 else {
3618 NS_LossyConvertUTF16toASCII cPath(iconPath);
3619 MOZ_LOG(gWindowsLog, LogLevel::Info,
3620 ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3621 ::GetLastError()));
3623 #endif
3624 if (smallIcon) {
3625 SetSmallIcon(smallIcon);
3627 #ifdef DEBUG_SetIcon
3628 else {
3629 NS_LossyConvertUTF16toASCII cPath(iconPath);
3630 MOZ_LOG(gWindowsLog, LogLevel::Info,
3631 ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3632 ::GetLastError()));
3634 #endif
3637 void nsWindow::SetBigIconNoData() {
3638 HICON bigIcon =
3639 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3640 SetBigIcon(bigIcon);
3643 void nsWindow::SetSmallIconNoData() {
3644 HICON smallIcon =
3645 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3646 SetSmallIcon(smallIcon);
3649 /**************************************************************
3651 * SECTION: nsIWidget::WidgetToScreenOffset
3653 * Return this widget's origin in screen coordinates.
3655 **************************************************************/
3657 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
3658 POINT point;
3659 point.x = 0;
3660 point.y = 0;
3661 ::ClientToScreen(mWnd, &point);
3662 return LayoutDeviceIntPoint(point.x, point.y);
3665 LayoutDeviceIntMargin nsWindow::ClientToWindowMargin() {
3666 if (mWindowType == WindowType::Popup) {
3667 return {};
3670 if (mCustomNonClient) {
3671 return NonClientSizeMargin(NormalWindowNonClientOffset());
3674 // Just use a dummy 200x200 at (200, 200) client rect as the rect.
3675 RECT clientRect;
3676 clientRect.left = 200;
3677 clientRect.top = 200;
3678 clientRect.right = 400;
3679 clientRect.bottom = 400;
3681 auto ToRect = [](const RECT& aRect) -> LayoutDeviceIntRect {
3682 return {aRect.left, aRect.top, aRect.right - aRect.left,
3683 aRect.bottom - aRect.top};
3686 RECT windowRect = clientRect;
3687 ::AdjustWindowRectEx(&windowRect, WindowStyle(), false, WindowExStyle());
3689 return ToRect(windowRect) - ToRect(clientRect);
3692 /**************************************************************
3694 * SECTION: nsIWidget::EnableDragDrop
3696 * Enables/Disables drag and drop of files on this widget.
3698 **************************************************************/
3700 void nsWindow::EnableDragDrop(bool aEnable) {
3701 if (!mWnd) {
3702 // Return early if the window already closed
3703 return;
3706 if (aEnable) {
3707 if (!mNativeDragTarget) {
3708 mNativeDragTarget = new nsNativeDragTarget(this);
3709 mNativeDragTarget->AddRef();
3710 ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
3712 } else {
3713 if (mWnd && mNativeDragTarget) {
3714 ::RevokeDragDrop(mWnd);
3715 mNativeDragTarget->DragCancel();
3716 NS_RELEASE(mNativeDragTarget);
3721 /**************************************************************
3723 * SECTION: nsIWidget::CaptureMouse
3725 * Enables/Disables system mouse capture.
3727 **************************************************************/
3729 void nsWindow::CaptureMouse(bool aCapture) {
3730 TRACKMOUSEEVENT mTrack;
3731 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
3732 mTrack.dwHoverTime = 0;
3733 mTrack.hwndTrack = mWnd;
3734 if (aCapture) {
3735 mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
3736 ::SetCapture(mWnd);
3737 } else {
3738 mTrack.dwFlags = TME_LEAVE;
3739 ::ReleaseCapture();
3741 sIsInMouseCapture = aCapture;
3742 TrackMouseEvent(&mTrack);
3745 /**************************************************************
3747 * SECTION: nsIWidget::CaptureRollupEvents
3749 * Dealing with event rollup on destroy for popups. Enables &
3750 * Disables system capture of any and all events that would
3751 * cause a dropdown to be rolled up.
3753 **************************************************************/
3755 void nsWindow::CaptureRollupEvents(bool aDoCapture) {
3756 if (aDoCapture) {
3757 if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
3758 RegisterSpecialDropdownHooks();
3760 sProcessHook = true;
3761 } else {
3762 sProcessHook = false;
3763 UnregisterSpecialDropdownHooks();
3767 /**************************************************************
3769 * SECTION: nsIWidget::GetAttention
3771 * Bring this window to the user's attention.
3773 **************************************************************/
3775 // Draw user's attention to this window until it comes to foreground.
3776 nsresult nsWindow::GetAttention(int32_t aCycleCount) {
3777 // Got window?
3778 if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
3780 HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
3781 HWND fgWnd = ::GetForegroundWindow();
3782 // Don't flash if the flash count is 0 or if the foreground window is our
3783 // window handle or that of our owned-most window.
3784 if (aCycleCount == 0 || flashWnd == fgWnd ||
3785 flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
3786 return NS_OK;
3789 DWORD defaultCycleCount = 0;
3790 ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
3792 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
3793 aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
3794 ::FlashWindowEx(&flashInfo);
3796 return NS_OK;
3799 void nsWindow::StopFlashing() {
3800 HWND flashWnd = mWnd;
3801 while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
3802 flashWnd = ownerWnd;
3805 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
3806 ::FlashWindowEx(&flashInfo);
3809 /**************************************************************
3811 * SECTION: nsIWidget::HasPendingInputEvent
3813 * Ask whether there user input events pending. All input events are
3814 * included, including those not targeted at this nsIwidget instance.
3816 **************************************************************/
3818 bool nsWindow::HasPendingInputEvent() {
3819 // If there is pending input or the user is currently
3820 // moving the window then return true.
3821 // Note: When the user is moving the window WIN32 spins
3822 // a separate event loop and input events are not
3823 // reported to the application.
3824 if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
3825 GUITHREADINFO guiInfo;
3826 guiInfo.cbSize = sizeof(GUITHREADINFO);
3827 if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
3828 return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
3831 /**************************************************************
3833 * SECTION: nsIWidget::GetWindowRenderer
3835 * Get the window renderer associated with this widget.
3837 **************************************************************/
3839 WindowRenderer* nsWindow::GetWindowRenderer() {
3840 if (mWindowRenderer) {
3841 return mWindowRenderer;
3844 if (!mLocalesChangedObserver) {
3845 mLocalesChangedObserver = new LocalesChangedObserver(this);
3848 // Try OMTC first.
3849 if (!mWindowRenderer && ShouldUseOffMainThreadCompositing()) {
3850 gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
3851 CreateCompositor();
3854 if (!mWindowRenderer) {
3855 MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
3856 MOZ_ASSERT(!mCompositorWidgetDelegate);
3858 // Ensure we have a widget proxy even if we're not using the compositor,
3859 // since all our transparent window handling lives there.
3860 WinCompositorWidgetInitData initData(
3861 reinterpret_cast<uintptr_t>(mWnd),
3862 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
3863 mTransparencyMode, mFrameState->GetSizeMode());
3864 // If we're not using the compositor, the options don't actually matter.
3865 CompositorOptions options(false, false);
3866 mBasicLayersSurface =
3867 new InProcessWinCompositorWidget(initData, options, this);
3868 mCompositorWidgetDelegate = mBasicLayersSurface;
3869 mWindowRenderer = CreateFallbackRenderer();
3872 NS_ASSERTION(mWindowRenderer, "Couldn't provide a valid window renderer.");
3874 if (mWindowRenderer) {
3875 // Update the size constraints now that the layer manager has been
3876 // created.
3877 KnowsCompositor* knowsCompositor = mWindowRenderer->AsKnowsCompositor();
3878 if (knowsCompositor) {
3879 SizeConstraints c = mSizeConstraints;
3880 mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
3881 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
3882 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
3883 nsBaseWidget::SetSizeConstraints(c);
3887 return mWindowRenderer;
3890 /**************************************************************
3892 * SECTION: nsBaseWidget::SetCompositorWidgetDelegate
3894 * Called to connect the nsWindow to the delegate providing
3895 * platform compositing API access.
3897 **************************************************************/
3899 void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
3900 if (delegate) {
3901 mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
3902 MOZ_ASSERT(mCompositorWidgetDelegate,
3903 "nsWindow::SetCompositorWidgetDelegate called with a "
3904 "non-PlatformCompositorWidgetDelegate");
3905 } else {
3906 mCompositorWidgetDelegate = nullptr;
3910 /**************************************************************
3912 * SECTION: nsIWidget::OnDefaultButtonLoaded
3914 * Called after the dialog is loaded and it has a default button.
3916 **************************************************************/
3918 nsresult nsWindow::OnDefaultButtonLoaded(
3919 const LayoutDeviceIntRect& aButtonRect) {
3920 if (aButtonRect.IsEmpty()) return NS_OK;
3922 // Don't snap when we are not active.
3923 HWND activeWnd = ::GetActiveWindow();
3924 if (activeWnd != ::GetForegroundWindow() ||
3925 WinUtils::GetTopLevelHWND(mWnd, true) !=
3926 WinUtils::GetTopLevelHWND(activeWnd, true)) {
3927 return NS_OK;
3930 bool isAlwaysSnapCursor =
3931 Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
3933 if (!isAlwaysSnapCursor) {
3934 BOOL snapDefaultButton;
3935 if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
3936 0) ||
3937 !snapDefaultButton)
3938 return NS_OK;
3941 LayoutDeviceIntRect widgetRect = GetScreenBounds();
3942 LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
3944 LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
3945 buttonRect.Y() + buttonRect.Height() / 2);
3946 // The center of the button can be outside of the widget.
3947 // E.g., it could be hidden by scrolling.
3948 if (!widgetRect.Contains(centerOfButton)) {
3949 return NS_OK;
3952 if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
3953 NS_ERROR("SetCursorPos failed");
3954 return NS_ERROR_FAILURE;
3956 return NS_OK;
3959 uint32_t nsWindow::GetMaxTouchPoints() const {
3960 return WinUtils::GetMaxTouchPoints();
3963 void nsWindow::SetWindowClass(const nsAString& xulWinType,
3964 const nsAString& xulWinClass,
3965 const nsAString& xulWinName) {
3966 mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank");
3969 /**************************************************************
3970 **************************************************************
3972 ** BLOCK: Moz Events
3974 ** Moz GUI event management.
3976 **************************************************************
3977 **************************************************************/
3979 /**************************************************************
3981 * SECTION: Mozilla event initialization
3983 * Helpers for initializing moz events.
3985 **************************************************************/
3987 // Event initialization
3988 void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
3989 if (nullptr == aPoint) { // use the point from the event
3990 // get the message position in client coordinates
3991 if (mWnd != nullptr) {
3992 DWORD pos = ::GetMessagePos();
3993 POINT cpos;
3995 cpos.x = GET_X_LPARAM(pos);
3996 cpos.y = GET_Y_LPARAM(pos);
3998 ::ScreenToClient(mWnd, &cpos);
3999 event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
4000 } else {
4001 event.mRefPoint = LayoutDeviceIntPoint(0, 0);
4003 } else {
4004 // use the point override if provided
4005 event.mRefPoint = *aPoint;
4008 event.AssignEventTime(CurrentMessageWidgetEventTime());
4011 WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
4012 LONG messageTime = ::GetMessageTime();
4013 return WidgetEventTime(GetMessageTimeStamp(messageTime));
4016 /**************************************************************
4018 * SECTION: Moz event dispatch helpers
4020 * Helpers for dispatching different types of moz events.
4022 **************************************************************/
4024 // Main event dispatch. Invokes callback and ProcessEvent method on
4025 // Event Listener object. Part of nsIWidget.
4026 nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
4027 nsEventStatus& aStatus) {
4028 #ifdef WIDGET_DEBUG_OUTPUT
4029 debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
4030 #endif // WIDGET_DEBUG_OUTPUT
4032 aStatus = nsEventStatus_eIgnore;
4034 // Top level windows can have a view attached which requires events be sent
4035 // to the underlying base window and the view. Added when we combined the
4036 // base chrome window with the main content child for nc client area (title
4037 // bar) rendering.
4038 if (mAttachedWidgetListener) {
4039 aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
4040 } else if (mWidgetListener) {
4041 aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
4044 // the window can be destroyed during processing of seemingly innocuous events
4045 // like, say, mousedowns due to the magic of scripting. mousedowns will return
4046 // nsEventStatus_eIgnore, which causes problems with the deleted window.
4047 // therefore:
4048 if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
4049 return NS_OK;
4052 bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
4053 WidgetGUIEvent event(true, aMsg, this);
4054 InitEvent(event);
4056 bool result = DispatchWindowEvent(event);
4057 return result;
4060 bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
4061 nsEventStatus status = DispatchInputEvent(event).mContentStatus;
4062 return ConvertStatus(status);
4065 bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
4066 nsEventStatus status;
4067 DispatchEvent(aEvent, status);
4068 return ConvertStatus(status);
4071 bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
4072 nsEventStatus status =
4073 DispatchInputEvent(aEvent->AsInputEvent()).mContentStatus;
4074 return ConvertStatus(status);
4077 // Recursively dispatch synchronous paints for nsIWidget
4078 // descendants with invalidated rectangles.
4079 BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
4080 LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
4081 if (proc == (LONG_PTR)&nsWindow::WindowProc) {
4082 // its one of our windows so check to see if it has a
4083 // invalidated rect. If it does. Dispatch a synchronous
4084 // paint.
4085 if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
4087 return TRUE;
4090 // Check for pending paints and dispatch any pending paint
4091 // messages for any nsIWidget which is a descendant of the
4092 // top-level window that *this* window is embedded within.
4094 // Note: We do not dispatch pending paint messages for non
4095 // nsIWidget managed windows.
4096 void nsWindow::DispatchPendingEvents() {
4097 // We need to ensure that reflow events do not get starved.
4098 // At the same time, we don't want to recurse through here
4099 // as that would prevent us from dispatching starved paints.
4100 static int recursionBlocker = 0;
4101 if (recursionBlocker++ == 0) {
4102 NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
4103 --recursionBlocker;
4106 // Quickly check to see if there are any paint events pending,
4107 // but only dispatch them if it has been long enough since the
4108 // last paint completed.
4109 if (::GetQueueStatus(QS_PAINT) &&
4110 ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
4111 // Find the top level window.
4112 HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
4114 // Dispatch pending paints for topWnd and all its descendant windows.
4115 // Note: EnumChildWindows enumerates all descendant windows not just
4116 // the children (but not the window itself).
4117 nsWindow::DispatchStarvedPaints(topWnd, 0);
4118 ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
4122 void nsWindow::DispatchCustomEvent(const nsString& eventName) {
4123 if (Document* doc = GetDocument()) {
4124 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
4125 win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
4130 bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
4131 LayoutDeviceIntPoint aEventPoint) {
4132 // Allow users to start dragging by double-tapping.
4133 if (aEventMessage == eMouseDoubleClick) {
4134 return true;
4137 // In chrome UI, allow touchdownstartsdrag attributes
4138 // to cause any touchdown event to trigger a drag.
4139 if (aEventMessage == eMouseDown) {
4140 WidgetMouseEvent hittest(true, eMouseHitTest, this,
4141 WidgetMouseEvent::eReal);
4142 hittest.mRefPoint = aEventPoint;
4143 hittest.mIgnoreRootScrollFrame = true;
4144 hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
4145 DispatchInputEvent(&hittest);
4147 if (EventTarget* target = hittest.GetDOMEventTarget()) {
4148 if (nsIContent* content = nsIContent::FromEventTarget(target)) {
4149 // Check if the element or any parent element has the
4150 // attribute we're looking for.
4151 for (Element* element = content->GetAsElementOrParentElement(); element;
4152 element = element->GetParentElement()) {
4153 nsAutoString startDrag;
4154 element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
4155 if (!startDrag.IsEmpty()) {
4156 return true;
4163 return false;
4166 // Deal with all sort of mouse event
4167 bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
4168 LPARAM lParam, bool aIsContextMenuKey,
4169 int16_t aButton, uint16_t aInputSource,
4170 WinPointerInfo* aPointerInfo,
4171 bool aIgnoreAPZ) {
4172 ContextMenuPreventer contextMenuPreventer(this);
4173 bool result = false;
4175 UserActivity();
4177 if (!mWidgetListener) {
4178 return result;
4181 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
4182 LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
4184 // Suppress mouse moves caused by widget creation. Make sure to do this early
4185 // so that we update sLastMouseMovePoint even for touch-induced mousemove
4186 // events.
4187 if (aEventMessage == eMouseMove) {
4188 if ((sLastMouseMovePoint.x == mpScreen.x.value) &&
4189 (sLastMouseMovePoint.y == mpScreen.y.value)) {
4190 return result;
4192 sLastMouseMovePoint.x = mpScreen.x;
4193 sLastMouseMovePoint.y = mpScreen.y;
4196 if (!aIgnoreAPZ && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
4197 if (mTouchWindow) {
4198 // If mTouchWindow is true, then we must have APZ enabled and be
4199 // feeding it raw touch events. In that case we only want to
4200 // send touch-generated mouse events to content if they should
4201 // start a touch-based drag-and-drop gesture, such as on
4202 // double-tapping or when tapping elements marked with the
4203 // touchdownstartsdrag attribute in chrome UI.
4204 MOZ_ASSERT(mAPZC);
4205 if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
4206 aEventMessage = eMouseTouchDrag;
4207 } else {
4208 return result;
4213 uint32_t pointerId =
4214 aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
4216 switch (aEventMessage) {
4217 case eMouseDown:
4218 CaptureMouse(true);
4219 break;
4221 // eMouseMove and eMouseExitFromWidget are here because we need to make
4222 // sure capture flag isn't left on after a drag where we wouldn't see a
4223 // button up message (see bug 324131).
4224 case eMouseUp:
4225 case eMouseMove:
4226 case eMouseExitFromWidget:
4227 if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
4228 sIsInMouseCapture)
4229 CaptureMouse(false);
4230 break;
4232 default:
4233 break;
4235 } // switch
4237 WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal,
4238 aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
4239 : WidgetMouseEvent::eNormal);
4240 if (aEventMessage == eContextMenu && aIsContextMenuKey) {
4241 LayoutDeviceIntPoint zero(0, 0);
4242 InitEvent(event, &zero);
4243 } else {
4244 InitEvent(event, &eventPoint);
4247 ModifierKeyState modifierKeyState;
4248 modifierKeyState.InitInputEvent(event);
4250 // eContextMenu with Shift state is special. It won't fire "contextmenu"
4251 // event in the web content for blocking web content to prevent its default.
4252 // However, Shift+F10 is a standard shortcut key on Windows. Therefore,
4253 // this should not block web page to prevent its default. I.e., it should
4254 // behave same as ContextMenu key without Shift key.
4255 // XXX Should we allow to block web page to prevent its default with
4256 // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
4257 if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() &&
4258 NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
4259 NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
4260 event.mModifiers &= ~MODIFIER_SHIFT;
4263 event.mButton = aButton;
4264 event.mInputSource = aInputSource;
4265 if (aPointerInfo) {
4266 // Mouse events from Windows WM_POINTER*. Fill more information in
4267 // WidgetMouseEvent.
4268 event.AssignPointerHelperData(*aPointerInfo);
4269 event.mPressure = aPointerInfo->mPressure;
4270 event.mButtons = aPointerInfo->mButtons;
4271 } else {
4272 // If we get here the mouse events must be from non-touch sources, so
4273 // convert it to pointer events as well
4274 event.convertToPointer = true;
4275 event.pointerId = pointerId;
4278 // Static variables used to distinguish simple-, double- and triple-clicks.
4279 static POINT sLastMousePoint = {0};
4280 static LONG sLastMouseDownTime = 0L;
4281 static LONG sLastClickCount = 0L;
4282 static BYTE sLastMouseButton = 0;
4284 bool insideMovementThreshold =
4285 (DeprecatedAbs(sLastMousePoint.x - eventPoint.x.value) <
4286 (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
4287 (DeprecatedAbs(sLastMousePoint.y - eventPoint.y.value) <
4288 (short)::GetSystemMetrics(SM_CYDOUBLECLK));
4290 BYTE eventButton;
4291 switch (aButton) {
4292 case MouseButton::ePrimary:
4293 eventButton = VK_LBUTTON;
4294 break;
4295 case MouseButton::eMiddle:
4296 eventButton = VK_MBUTTON;
4297 break;
4298 case MouseButton::eSecondary:
4299 eventButton = VK_RBUTTON;
4300 break;
4301 default:
4302 eventButton = 0;
4303 break;
4306 // Doubleclicks are used to set the click count, then changed to mousedowns
4307 // We're going to time double-clicks from mouse *up* to next mouse *down*
4308 LONG curMsgTime = ::GetMessageTime();
4310 switch (aEventMessage) {
4311 case eMouseDoubleClick:
4312 event.mMessage = eMouseDown;
4313 event.mButton = aButton;
4314 sLastClickCount = 2;
4315 sLastMouseDownTime = curMsgTime;
4316 break;
4317 case eMouseUp:
4318 // remember when this happened for the next mouse down
4319 sLastMousePoint.x = eventPoint.x;
4320 sLastMousePoint.y = eventPoint.y;
4321 sLastMouseButton = eventButton;
4322 break;
4323 case eMouseDown:
4324 // now look to see if we want to convert this to a double- or triple-click
4325 if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
4326 insideMovementThreshold && eventButton == sLastMouseButton) {
4327 sLastClickCount++;
4328 } else {
4329 // reset the click count, to count *this* click
4330 sLastClickCount = 1;
4332 // Set last Click time on MouseDown only
4333 sLastMouseDownTime = curMsgTime;
4334 break;
4335 case eMouseMove:
4336 if (!insideMovementThreshold) {
4337 sLastClickCount = 0;
4339 break;
4340 case eMouseExitFromWidget:
4341 event.mExitFrom =
4342 Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
4343 : WidgetMouseEvent::ePlatformChild);
4344 break;
4345 default:
4346 break;
4348 event.mClickCount = sLastClickCount;
4350 #ifdef NS_DEBUG_XX
4351 MOZ_LOG(gWindowsLog, LogLevel::Info,
4352 ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount));
4353 #endif
4355 // call the event callback
4356 if (mWidgetListener) {
4357 if (aEventMessage == eMouseMove) {
4358 LayoutDeviceIntRect rect = GetBounds();
4359 rect.MoveTo(0, 0);
4361 if (rect.Contains(event.mRefPoint)) {
4362 if (sCurrentWindow == nullptr || sCurrentWindow != this) {
4363 if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
4364 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4365 sCurrentWindow->DispatchMouseEvent(
4366 eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
4367 aInputSource, aPointerInfo);
4369 sCurrentWindow = this;
4370 if (!mInDtor) {
4371 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4372 sCurrentWindow->DispatchMouseEvent(
4373 eMouseEnterIntoWidget, wParam, pos, false,
4374 MouseButton::ePrimary, aInputSource, aPointerInfo);
4378 } else if (aEventMessage == eMouseExitFromWidget) {
4379 if (sCurrentWindow == this) {
4380 sCurrentWindow = nullptr;
4384 nsIWidget::ContentAndAPZEventStatus eventStatus =
4385 DispatchInputEvent(&event);
4386 contextMenuPreventer.Update(event, eventStatus);
4387 return ConvertStatus(eventStatus.mContentStatus);
4390 return result;
4393 HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
4394 // retrieve the toplevel window or dialogue
4395 HWND toplevelWnd = nullptr;
4396 while (aCurWnd) {
4397 toplevelWnd = aCurWnd;
4398 nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
4399 if (win) {
4400 if (win->mWindowType == WindowType::TopLevel ||
4401 win->mWindowType == WindowType::Dialog) {
4402 break;
4406 aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
4408 return toplevelWnd;
4411 void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
4412 if (aIsActivate && mPickerDisplayCount) {
4413 // We disable the root window when a picker opens. See PickerOpen. When the
4414 // picker closes (but before PickerClosed is called), our window will get
4415 // focus, but it will still be disabled. This confuses the focus system.
4416 // Therefore, we ignore this focus and explicitly call this function once
4417 // we re-enable the window. Rarely, the picker seems to re-enable our root
4418 // window before we do, but for simplicity, we always ignore focus before
4419 // the final call to PickerClosed. See bug 1883568 for further details.
4420 return;
4423 if (aIsActivate) {
4424 sJustGotActivate = false;
4426 sJustGotDeactivate = false;
4427 mLastKillFocusWindow = nullptr;
4429 HWND toplevelWnd = GetTopLevelForFocus(mWnd);
4431 if (toplevelWnd) {
4432 nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
4433 if (win && win->mWidgetListener) {
4434 if (aIsActivate) {
4435 win->mWidgetListener->WindowActivated();
4436 } else {
4437 win->mWidgetListener->WindowDeactivated();
4443 HWND nsWindow::WindowAtMouse() {
4444 DWORD pos = ::GetMessagePos();
4445 POINT mp;
4446 mp.x = GET_X_LPARAM(pos);
4447 mp.y = GET_Y_LPARAM(pos);
4448 return ::WindowFromPoint(mp);
4451 bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
4452 HWND mouseWnd = WindowAtMouse();
4454 // WinUtils::GetTopLevelHWND() will return a HWND for the window frame
4455 // (which includes the non-client area). If the mouse has moved into
4456 // the non-client area, we should treat it as a top-level exit.
4457 HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
4458 if (mouseWnd == mouseTopLevel) return true;
4460 return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
4463 /**************************************************************
4465 * SECTION: IPC
4467 * IPC related helpers.
4469 **************************************************************/
4471 // static
4472 bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
4473 switch (aMsg) {
4474 case WM_SETFOCUS:
4475 case WM_KILLFOCUS:
4476 case WM_ENABLE:
4477 case WM_WINDOWPOSCHANGING:
4478 case WM_WINDOWPOSCHANGED:
4479 case WM_PARENTNOTIFY:
4480 case WM_ACTIVATEAPP:
4481 case WM_NCACTIVATE:
4482 case WM_ACTIVATE:
4483 case WM_CHILDACTIVATE:
4484 case WM_IME_SETCONTEXT:
4485 case WM_IME_NOTIFY:
4486 case WM_SHOWWINDOW:
4487 case WM_CANCELMODE:
4488 case WM_MOUSEACTIVATE:
4489 case WM_CONTEXTMENU:
4490 aResult = 0;
4491 return true;
4493 case WM_SETTINGCHANGE:
4494 case WM_SETCURSOR:
4495 return false;
4498 #ifdef DEBUG
4499 char szBuf[200];
4500 sprintf(szBuf,
4501 "An unhandled ISMEX_SEND message was received during spin loop! (%X)",
4502 aMsg);
4503 NS_WARNING(szBuf);
4504 #endif
4506 return false;
4509 void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
4510 MOZ_ASSERT_IF(
4511 msg != WM_GETOBJECT,
4512 !mozilla::ipc::MessageChannel::IsPumpingMessages() ||
4513 mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
4515 // Modal UI being displayed in windowless plugins.
4516 if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
4517 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4518 LRESULT res;
4519 if (IsAsyncResponseEvent(msg, res)) {
4520 ReplyMessage(res);
4522 return;
4525 // Handle certain sync plugin events sent to the parent which
4526 // trigger ipc calls that result in deadlocks.
4528 DWORD dwResult = 0;
4529 bool handled = false;
4531 switch (msg) {
4532 // Windowless flash sending WM_ACTIVATE events to the main window
4533 // via calls to ShowWindow.
4534 case WM_ACTIVATE:
4535 if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
4536 IsWindow((HWND)lParam)) {
4537 // Check for Adobe Reader X sync activate message from their
4538 // helper window and ignore. Fixes an annoying focus problem.
4539 if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
4540 ISMEX_SEND) {
4541 wchar_t szClass[10];
4542 HWND focusWnd = (HWND)lParam;
4543 if (IsWindowVisible(focusWnd) &&
4544 GetClassNameW(focusWnd, szClass,
4545 sizeof(szClass) / sizeof(char16_t)) &&
4546 !wcscmp(szClass, L"Edit") &&
4547 !WinUtils::IsOurProcessWindow(focusWnd)) {
4548 break;
4551 handled = true;
4553 break;
4554 // Plugins taking or losing focus triggering focus app messages.
4555 case WM_SETFOCUS:
4556 case WM_KILLFOCUS:
4557 // Windowed plugins that pass sys key events to defwndproc generate
4558 // WM_SYSCOMMAND events to the main window.
4559 case WM_SYSCOMMAND:
4560 // Windowed plugins that fire context menu selection events to parent
4561 // windows.
4562 case WM_CONTEXTMENU:
4563 // IME events fired as a result of synchronous focus changes
4564 case WM_IME_SETCONTEXT:
4565 handled = true;
4566 break;
4569 if (handled &&
4570 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4571 ReplyMessage(dwResult);
4575 /**************************************************************
4576 **************************************************************
4578 ** BLOCK: Native events
4580 ** Main Windows message handlers and OnXXX handlers for
4581 ** Windows event handling.
4583 **************************************************************
4584 **************************************************************/
4586 /**************************************************************
4588 * SECTION: Wind proc.
4590 * The main Windows event procedures and associated
4591 * message processing methods.
4593 **************************************************************/
4595 static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
4596 int32_t x, int32_t y) {
4597 HMENU hMenu = GetSystemMenu(hWnd, FALSE);
4598 if (hMenu) {
4599 MENUITEMINFO mii;
4600 mii.cbSize = sizeof(MENUITEMINFO);
4601 mii.fMask = MIIM_STATE;
4602 mii.fType = 0;
4604 // update the options
4605 mii.fState = MF_ENABLED;
4606 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4607 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4608 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4609 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4610 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4612 mii.fState = MF_GRAYED;
4613 switch (sizeMode) {
4614 case nsSizeMode_Fullscreen:
4615 // intentional fall through
4616 case nsSizeMode_Maximized:
4617 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4618 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4619 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4620 break;
4621 case nsSizeMode_Minimized:
4622 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4623 break;
4624 case nsSizeMode_Normal:
4625 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4626 break;
4627 case nsSizeMode_Invalid:
4628 NS_ASSERTION(false, "Did the argument come from invalid IPC?");
4629 break;
4630 default:
4631 MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected");
4632 break;
4634 LPARAM cmd = TrackPopupMenu(
4635 hMenu,
4636 (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN |
4637 (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
4638 x, y, 0, hWnd, nullptr);
4639 if (cmd) {
4640 PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
4641 return true;
4644 return false;
4647 // The WndProc procedure for all nsWindows in this toolkit. This merely catches
4648 // SEH exceptions and passes the real work to WindowProcInternal. See bug 587406
4649 // and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
4650 LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
4651 LPARAM lParam) {
4652 mozilla::ipc::CancelCPOWs();
4654 BackgroundHangMonitor().NotifyActivity();
4656 return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
4657 wParam, lParam);
4660 LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
4661 WPARAM wParam, LPARAM lParam) {
4662 if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
4663 // This message was sent to the FAKETRACKPOINTSCROLLABLE.
4664 if (msg == WM_HSCROLL) {
4665 // Route WM_HSCROLL messages to the main window.
4666 hWnd = ::GetParent(::GetParent(hWnd));
4667 } else {
4668 // Handle all other messages with its original window procedure.
4669 WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
4670 return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
4674 if (msg == MOZ_WM_TRACE) {
4675 // This is a tracer event for measuring event loop latency.
4676 // See WidgetTraceEvent.cpp for more details.
4677 mozilla::SignalTracerThread();
4678 return 0;
4681 // Get the window which caused the event and ask it to process the message
4682 nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
4683 NS_ASSERTION(targetWindow, "nsWindow* is null!");
4684 if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
4686 // Hold the window for the life of this method, in case it gets
4687 // destroyed during processing, unless we're in the dtor already.
4688 nsCOMPtr<nsIWidget> kungFuDeathGrip;
4689 if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
4691 targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
4693 // Create this here so that we store the last rolled up popup until after
4694 // the event has been processed.
4695 nsAutoRollup autoRollup;
4697 LRESULT popupHandlingResult;
4698 if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult))
4699 return popupHandlingResult;
4701 // Call ProcessMessage
4702 LRESULT retValue;
4703 if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
4704 return retValue;
4707 LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
4708 wParam, lParam);
4710 return res;
4713 const char16_t* GetQuitType() {
4714 if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
4715 DWORD cchCmdLine = 0;
4716 HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
4717 &cchCmdLine, nullptr);
4718 if (rc == S_OK) {
4719 return u"os-restart";
4722 return nullptr;
4725 bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
4726 LPARAM& aLParam,
4727 MSGResult& aResult) {
4728 if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
4729 return true;
4732 if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
4733 return true;
4736 if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
4737 aResult)) {
4738 return true;
4741 return false;
4744 // The main windows message processing method. Wraps ProcessMessageInternal so
4745 // we can log aRetValue.
4746 bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
4747 LRESULT* aRetValue) {
4748 // For some events we might change the parameter values, so log
4749 // before and after we process them.
4750 NativeEventLogger eventLogger("nsWindow", mWnd, msg, wParam, lParam);
4751 bool result = ProcessMessageInternal(msg, wParam, lParam, aRetValue);
4752 eventLogger.SetResult(*aRetValue, result);
4754 return result;
4757 // The main windows message processing method. Called by ProcessMessage.
4758 bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
4759 LRESULT* aRetValue) {
4760 MSGResult msgResult(aRetValue);
4761 if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
4762 return (msgResult.mConsumed || !mWnd);
4765 bool result = false; // call the default nsWindow proc
4766 *aRetValue = 0;
4768 // The DWM resize hack (see bug 1763981) causes us to process a number of
4769 // messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
4770 // would ordinarily result in a whole lot of internal state being updated.
4772 // Since we're supposed to end in the same state we started in (and since the
4773 // content shouldn't know about any of this nonsense), just discard any
4774 // messages synchronously dispatched from within the hack.
4775 if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
4776 return true;
4779 // Glass hit testing w/custom transparent margins.
4781 // FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
4782 // now.
4783 LRESULT dwmHitResult;
4784 if (mCustomNonClient &&
4785 DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
4786 *aRetValue = dwmHitResult;
4787 return true;
4790 // The preference whether to use a different keyboard layout for each
4791 // window is cached, and updating it will not take effect until the
4792 // next restart. We read the preference here and not upon WM_ACTIVATE to make
4793 // sure that this behavior is consistent. Otherwise, if the user changed the
4794 // preference before having ever lowered the window, the preference would take
4795 // effect immediately.
4796 static const bool sSwitchKeyboardLayout =
4797 Preferences::GetBool("intl.keyboard.per_window_layout", false);
4798 AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
4800 // (Large blocks of code should be broken out into OnEvent handlers.)
4801 switch (msg) {
4802 // WM_QUERYENDSESSION must be handled by all windows.
4803 // Otherwise Windows thinks the window can just be killed at will.
4804 case WM_QUERYENDSESSION: {
4805 // Ask around if it's ok to quit.
4806 nsCOMPtr<nsIObserverService> obsServ =
4807 mozilla::services::GetObserverService();
4808 nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
4809 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
4810 cancelQuitWrapper->SetData(false);
4812 const char16_t* quitType = GetQuitType();
4813 obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
4814 quitType);
4816 bool shouldCancelQuit;
4817 cancelQuitWrapper->GetData(&shouldCancelQuit);
4818 *aRetValue = !shouldCancelQuit;
4819 result = true;
4820 } break;
4822 case MOZ_WM_STARTA11Y:
4823 #if defined(ACCESSIBILITY)
4824 Unused << GetAccessible();
4825 result = true;
4826 #else
4827 result = false;
4828 #endif
4829 break;
4831 case WM_ENDSESSION: {
4832 // For WM_ENDSESSION, wParam indicates whether we need to shutdown
4833 // (TRUE) or not (FALSE).
4834 if (!wParam) {
4835 result = true;
4836 break;
4838 // According to WM_ENDSESSION lParam documentation:
4839 // 0 -> OS shutdown or restart (no way to distinguish)
4840 // ENDSESSION_LOGOFF -> User is logging off
4841 // ENDSESSION_CLOSEAPP -> Application must shutdown
4842 // ENDSESSION_CRITICAL -> Application is forced to shutdown
4843 // The difference of the last two is not very clear.
4844 if (lParam == 0) {
4845 shutdownReason = AppShutdownReason::OSShutdown;
4846 } else if (lParam & ENDSESSION_LOGOFF) {
4847 shutdownReason = AppShutdownReason::OSSessionEnd;
4848 } else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
4849 shutdownReason = AppShutdownReason::OSForceClose;
4850 } else {
4851 MOZ_DIAGNOSTIC_ASSERT(false,
4852 "Received WM_ENDSESSION with unknown flags.");
4853 shutdownReason = AppShutdownReason::OSForceClose;
4856 [[fallthrough]];
4857 case MOZ_WM_APP_QUIT: {
4858 if (shutdownReason == AppShutdownReason::Unknown) {
4859 // TODO: We do not expect that these days anybody sends us
4860 // MOZ_WM_APP_QUIT, see bug 1827807.
4861 shutdownReason = AppShutdownReason::WinUnexpectedMozQuit;
4863 // Let's fake a shutdown sequence without actually closing windows etc.
4864 // to avoid Windows killing us in the middle. A proper shutdown would
4865 // require having a chance to pump some messages. Unfortunately
4866 // Windows won't let us do that. Bug 212316.
4867 nsCOMPtr<nsIObserverService> obsServ =
4868 mozilla::services::GetObserverService();
4869 const char16_t* syncShutdown = u"syncShutdown";
4870 const char16_t* quitType = GetQuitType();
4872 AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
4874 obsServ->NotifyObservers(nullptr, "quit-application-granted",
4875 syncShutdown);
4876 obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
4878 AppShutdown::OnShutdownConfirmed();
4880 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
4881 quitType);
4882 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
4883 nullptr);
4884 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
4885 nullptr);
4886 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
4887 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
4888 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
4889 nullptr);
4891 AppShutdown::DoImmediateExit();
4892 MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
4893 } break;
4895 case WM_SYSCOLORCHANGE:
4896 // No need to invalidate layout for system color changes, but we need to
4897 // invalidate style.
4898 NotifyThemeChanged(widget::ThemeChangeKind::Style);
4899 break;
4901 case WM_THEMECHANGED: {
4902 // Update non-client margin offsets
4903 UpdateNonClientMargins();
4904 nsUXThemeData::UpdateNativeThemeInfo();
4906 // We assume pretty much everything could've changed here.
4907 NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
4909 UpdateDarkModeToolbar();
4911 // Invalidate the window so that the repaint will
4912 // pick up the new theme.
4913 Invalidate(true, true, true);
4914 } break;
4916 case WM_WTSSESSION_CHANGE: {
4917 switch (wParam) {
4918 case WTS_CONSOLE_CONNECT:
4919 case WTS_REMOTE_CONNECT:
4920 case WTS_SESSION_UNLOCK:
4921 // When a session becomes visible, we should invalidate.
4922 Invalidate(true, true, true);
4923 break;
4924 default:
4925 break;
4927 } break;
4929 case WM_FONTCHANGE: {
4930 // We only handle this message for the hidden window,
4931 // as we only need to update the (global) font list once
4932 // for any given change, not once per window!
4933 if (mWindowType != WindowType::Invisible) {
4934 break;
4937 // update the global font list
4938 gfxPlatform::GetPlatform()->UpdateFontList();
4939 } break;
4941 case WM_SETTINGCHANGE: {
4942 if (wParam == SPI_SETCLIENTAREAANIMATION ||
4943 wParam == SPI_SETKEYBOARDDELAY || wParam == SPI_SETMOUSEVANISH ||
4944 wParam == MOZ_SPI_SETCURSORSIZE) {
4945 // These need to update LookAndFeel cached values.
4946 // They affect reduced motion settings / caret blink count / show
4947 // pointer while typing / tooltip offset, so no need to invalidate style
4948 // / layout.
4949 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
4950 break;
4952 if (wParam == SPI_SETFONTSMOOTHING ||
4953 wParam == SPI_SETFONTSMOOTHINGTYPE) {
4954 gfxDWriteFont::UpdateSystemTextVars();
4955 break;
4957 if (wParam == SPI_SETWORKAREA) {
4958 // NB: We also refresh screens on WM_DISPLAYCHANGE but the rcWork
4959 // values are sometimes wrong at that point. This message then
4960 // arrives soon afterward, when we can get the right rcWork values.
4961 ScreenHelperWin::RefreshScreens();
4962 break;
4964 if (auto lParamString = reinterpret_cast<const wchar_t*>(lParam)) {
4965 if (!wcscmp(lParamString, L"ImmersiveColorSet")) {
4966 // This affects system colors (-moz-win-accentcolor), so gotta pass
4967 // the style flag.
4968 NotifyThemeChanged(widget::ThemeChangeKind::Style);
4969 break;
4972 // UserInteractionMode, ConvertibleSlateMode, SystemDockMode may cause
4973 // @media(pointer) queries to change, which layout needs to know about
4975 // (WM_SETTINGCHANGE will be sent to all top-level windows, so we
4976 // only respond to the hidden top-level window to avoid hammering
4977 // layout with a bunch of NotifyThemeChanged() calls)
4979 if (mWindowType == WindowType::Invisible) {
4980 if (!wcscmp(lParamString, L"UserInteractionMode") ||
4981 !wcscmp(lParamString, L"ConvertibleSlateMode") ||
4982 !wcscmp(lParamString, L"SystemDockMode")) {
4983 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
4984 WindowsUIUtils::UpdateInTabletMode();
4988 } break;
4990 case WM_DEVICECHANGE: {
4991 if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
4992 DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
4993 // Check dbch_devicetype explicitly since we will get other device types
4994 // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify
4995 // DBT_DEVTYP_DEVICEINTERFACE in the filter for
4996 // RegisterDeviceNotification.
4997 if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
4998 // This can only change media queries (any-hover/any-pointer).
4999 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5002 } break;
5004 case WM_NCCALCSIZE: {
5005 // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
5006 // will need to be kept in sync.
5007 if (mCustomNonClient) {
5008 // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
5009 // the proposed window rectangle for our window. During our
5010 // processing of the `WM_NCCALCSIZE` message, we are expected to
5011 // modify the `RECT` that `lParam` points to, so that its value upon
5012 // our return is the new client area. We must return 0 if `wParam`
5013 // is `FALSE`.
5015 // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
5016 // struct. This struct contains an array of 3 `RECT`s, the first of
5017 // which has the exact same meaning as the `RECT` that is pointed to
5018 // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
5019 // conjunction with our return value, can
5020 // be used to specify portions of the source and destination window
5021 // rectangles that are valid and should be preserved. We opt not to
5022 // implement an elaborate client-area preservation technique, and
5023 // simply return 0, which means "preserve the entire old client area
5024 // and align it with the upper-left corner of our new client area".
5025 RECT* clientRect =
5026 wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
5027 : (reinterpret_cast<RECT*>(lParam));
5028 auto margin = NonClientSizeMargin();
5029 clientRect->top += margin.top;
5030 clientRect->left += margin.left;
5031 clientRect->right -= margin.right;
5032 clientRect->bottom -= margin.bottom;
5033 // Make client rect's width and height more than 0 to
5034 // avoid problems of webrender and angle.
5035 clientRect->right = std::max(clientRect->right, clientRect->left + 1);
5036 clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
5038 result = true;
5039 *aRetValue = 0;
5041 break;
5044 case WM_GETTITLEBARINFOEX: {
5045 if (!mCustomNonClient) {
5046 break;
5048 auto* info = reinterpret_cast<TITLEBARINFOEX*>(lParam);
5049 const LayoutDeviceIntPoint origin = WidgetToScreenOffset();
5050 auto GeckoClientToWinScreenRect =
5051 [&origin](LayoutDeviceIntRect aRect) -> RECT {
5052 aRect.MoveBy(origin);
5053 return {
5054 .left = aRect.x,
5055 .top = aRect.y,
5056 .right = aRect.XMost(),
5057 .bottom = aRect.YMost(),
5060 auto SetButton = [&](size_t aIndex, WindowButtonType aType) {
5061 info->rgrect[aIndex] =
5062 GeckoClientToWinScreenRect(mWindowBtnRect[aType]);
5063 DWORD& state = info->rgstate[aIndex];
5064 if (mWindowBtnRect[aType].IsEmpty()) {
5065 state = STATE_SYSTEM_INVISIBLE;
5066 } else {
5067 state = STATE_SYSTEM_FOCUSABLE;
5070 info->rgrect[0] = info->rcTitleBar =
5071 GeckoClientToWinScreenRect(mDraggableRegion.GetBounds());
5072 info->rgstate[0] = 0;
5073 SetButton(2, WindowButtonType::Minimize);
5074 SetButton(3, WindowButtonType::Maximize);
5075 SetButton(5, WindowButtonType::Close);
5076 // We don't have a help button.
5077 info->rgstate[4] = STATE_SYSTEM_INVISIBLE;
5078 info->rgrect[4] = {0, 0, 0, 0};
5079 result = true;
5080 } break;
5082 case WM_NCHITTEST: {
5083 if (mInputRegion.mFullyTransparent) {
5084 // Treat this window as transparent.
5085 *aRetValue = HTTRANSPARENT;
5086 result = true;
5087 break;
5090 if (mInputRegion.mMargin) {
5091 const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
5092 GET_Y_LPARAM(lParam));
5093 LayoutDeviceIntRect screenRect = GetScreenBounds();
5094 screenRect.Deflate(mInputRegion.mMargin);
5095 if (!screenRect.Contains(screenPoint)) {
5096 *aRetValue = HTTRANSPARENT;
5097 result = true;
5098 break;
5103 * If an nc client area margin has been moved, we are responsible
5104 * for calculating where the resize margins are and returning the
5105 * appropriate set of hit test constants. DwmDefWindowProc (above)
5106 * will handle hit testing on it's command buttons if we are on a
5107 * composited desktop.
5110 if (!mCustomNonClient) {
5111 break;
5114 *aRetValue =
5115 ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5116 result = true;
5117 break;
5120 case WM_SETTEXT:
5122 * WM_SETTEXT paints the titlebar area. Avoid this if we have a
5123 * custom titlebar we paint ourselves, or if we're the ones
5124 * sending the message with an updated title
5127 if (mSendingSetText || !mCustomNonClient || mNonClientMargins.top == -1)
5128 break;
5131 // From msdn, the way around this is to disable the visible state
5132 // temporarily. We need the text to be set but we don't want the
5133 // redraw to occur. However, we need to make sure that we don't
5134 // do this at the same time that a Present is happening.
5136 // To do this we take mPresentLock in nsWindow::PreRender and
5137 // if that lock is taken we wait before doing WM_SETTEXT
5138 if (mCompositorWidgetDelegate) {
5139 mCompositorWidgetDelegate->EnterPresentLock();
5141 DWORD style = GetWindowLong(mWnd, GWL_STYLE);
5142 SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
5143 *aRetValue =
5144 CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
5145 SetWindowLong(mWnd, GWL_STYLE, style);
5146 if (mCompositorWidgetDelegate) {
5147 mCompositorWidgetDelegate->LeavePresentLock();
5150 return true;
5153 case WM_NCACTIVATE: {
5155 * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting
5156 * through WM_NCPAINT via InvalidateNonClientRegion.
5158 UpdateGetWindowInfoCaptionStatus(FALSE != wParam);
5160 if (!mCustomNonClient) {
5161 break;
5164 // There is a case that rendered result is not kept. Bug 1237617
5165 if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
5166 NS_DispatchToMainThread(NewRunnableMethod(
5167 "nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
5170 // let the dwm handle nc painting on glass
5171 // Never allow native painting if we are on fullscreen
5172 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) break;
5174 if (wParam == TRUE) {
5175 // going active
5176 *aRetValue = FALSE; // ignored
5177 result = true;
5178 // invalidate to trigger a paint
5179 InvalidateNonClientRegion();
5180 break;
5181 } else {
5182 // going inactive
5183 *aRetValue = TRUE; // go ahead and deactive
5184 result = true;
5185 // invalidate to trigger a paint
5186 InvalidateNonClientRegion();
5187 break;
5191 case WM_NCPAINT: {
5193 * ClearType changes often don't send a WM_SETTINGCHANGE message. But they
5194 * do seem to always send a WM_NCPAINT message, so let's update on that.
5196 gfxDWriteFont::UpdateSystemTextVars();
5197 } break;
5199 case WM_POWERBROADCAST:
5200 switch (wParam) {
5201 case PBT_APMSUSPEND:
5202 PostSleepWakeNotification(true);
5203 break;
5204 case PBT_APMRESUMEAUTOMATIC:
5205 case PBT_APMRESUMECRITICAL:
5206 case PBT_APMRESUMESUSPEND:
5207 PostSleepWakeNotification(false);
5208 break;
5210 break;
5212 case WM_CLOSE: // close request
5213 if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
5214 result = true; // abort window closure
5215 break;
5217 case WM_DESTROY:
5218 // clean up.
5219 DestroyLayerManager();
5220 OnDestroy();
5221 result = true;
5222 break;
5224 case WM_PAINT:
5225 *aRetValue = (int)OnPaint(0);
5226 result = true;
5227 break;
5229 case WM_HOTKEY:
5230 result = OnHotKey(wParam, lParam);
5231 break;
5233 case WM_SYSCHAR:
5234 case WM_CHAR: {
5235 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5236 result = ProcessCharMessage(nativeMsg, nullptr);
5237 DispatchPendingEvents();
5238 } break;
5240 case WM_SYSKEYUP:
5241 case WM_KEYUP: {
5242 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5243 nativeMsg.time = ::GetMessageTime();
5244 result = ProcessKeyUpMessage(nativeMsg, nullptr);
5245 DispatchPendingEvents();
5246 } break;
5248 case WM_SYSKEYDOWN:
5249 case WM_KEYDOWN: {
5250 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5251 result = ProcessKeyDownMessage(nativeMsg, nullptr);
5252 DispatchPendingEvents();
5253 } break;
5255 // Say we've dealt with erasing the background. (This is actually handled in
5256 // WM_PAINT, where necessary.)
5257 case WM_ERASEBKGND: {
5258 *aRetValue = 1;
5259 result = true;
5260 } break;
5262 case WM_MOUSEMOVE: {
5263 LPARAM lParamScreen = lParamToScreen(lParam);
5264 mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
5265 GET_Y_LPARAM(lParamScreen));
5267 if (!mMousePresent && !sIsInMouseCapture) {
5268 // First MOUSEMOVE over the client area. Ask for MOUSELEAVE
5269 TRACKMOUSEEVENT mTrack;
5270 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5271 mTrack.dwFlags = TME_LEAVE;
5272 mTrack.dwHoverTime = 0;
5273 mTrack.hwndTrack = mWnd;
5274 TrackMouseEvent(&mTrack);
5276 mMousePresent = true;
5278 // Suppress dispatch of pending events
5279 // when mouse moves are generated by widget
5280 // creation instead of user input.
5281 POINT mp;
5282 mp.x = GET_X_LPARAM(lParamScreen);
5283 mp.y = GET_Y_LPARAM(lParamScreen);
5284 bool userMovedMouse = false;
5285 if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
5286 userMovedMouse = true;
5289 if (userMovedMouse) {
5290 result = DispatchMouseEvent(
5291 eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
5292 MOUSE_INPUT_SOURCE(),
5293 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5294 DispatchPendingEvents();
5296 } break;
5298 case WM_NCMOUSEMOVE: {
5299 LPARAM lParamClient = lParamToClient(lParam);
5300 if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
5301 if (!sIsInMouseCapture) {
5302 TRACKMOUSEEVENT mTrack;
5303 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5304 mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
5305 mTrack.dwHoverTime = 0;
5306 mTrack.hwndTrack = mWnd;
5307 TrackMouseEvent(&mTrack);
5309 // If we noticed the mouse moving in our draggable region, forward the
5310 // message as a normal WM_MOUSEMOVE.
5311 SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
5312 } else {
5313 // We've transitioned from a draggable area to somewhere else within
5314 // the non-client area - perhaps one of the edges of the window for
5315 // resizing.
5316 mSimulatedClientArea = false;
5319 if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
5320 SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
5322 } break;
5324 case WM_LBUTTONDOWN: {
5325 result =
5326 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5327 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5328 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5329 DispatchPendingEvents();
5330 } break;
5332 case WM_LBUTTONUP: {
5333 result =
5334 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5335 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5336 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5337 DispatchPendingEvents();
5338 } break;
5340 case WM_NCMOUSELEAVE: {
5341 mSimulatedClientArea = false;
5343 if (EventIsInsideWindow(this)) {
5344 // If we're handling WM_NCMOUSELEAVE and the mouse is still over the
5345 // window, then by process of elimination, the mouse has moved from the
5346 // non-client to client area, so no need to fall-through to the
5347 // WM_MOUSELEAVE handler. We also need to re-register for the
5348 // WM_MOUSELEAVE message, since according to the documentation at [1],
5349 // all tracking requested via TrackMouseEvent is cleared once
5350 // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
5351 // [1]:
5352 // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
5353 TRACKMOUSEEVENT mTrack;
5354 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5355 mTrack.dwFlags = TME_LEAVE;
5356 mTrack.dwHoverTime = 0;
5357 mTrack.hwndTrack = mWnd;
5358 TrackMouseEvent(&mTrack);
5359 break;
5361 // We've transitioned from non-client to outside of the window, so
5362 // fall-through to the WM_MOUSELEAVE handler.
5363 [[fallthrough]];
5365 case WM_MOUSELEAVE: {
5366 if (!mMousePresent) break;
5367 if (mSimulatedClientArea) break;
5368 mMousePresent = false;
5370 // Check if the mouse is over the fullscreen transition window, if so
5371 // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
5372 // transition window disappears will not be ignored, even if the mouse
5373 // hasn't moved.
5374 if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
5375 sLastMouseMovePoint = {0};
5378 // We need to check mouse button states and put them in for
5379 // wParam.
5380 WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
5381 (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
5382 (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
5383 // Synthesize an event position because we don't get one from
5384 // WM_MOUSELEAVE.
5385 LPARAM pos = lParamToClient(::GetMessagePos());
5386 DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
5387 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5388 } break;
5390 case WM_CONTEXTMENU: {
5391 // If the context menu is brought up by a touch long-press, then
5392 // the APZ code is responsible for dealing with this, so we don't
5393 // need to do anything.
5394 if (mTouchWindow &&
5395 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
5396 MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
5397 result = true;
5398 break;
5401 // If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
5402 // event in overscroll gutter, we shouldn't open context menu.
5403 if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
5404 mNeedsToPreventContextMenu) {
5405 result = true;
5406 break;
5409 // if the context menu is brought up from the keyboard, |lParam|
5410 // will be -1.
5411 LPARAM pos;
5412 bool contextMenukey = false;
5413 if (lParam == -1) {
5414 contextMenukey = true;
5415 pos = lParamToClient(GetMessagePos());
5416 } else {
5417 pos = lParamToClient(lParam);
5420 result = DispatchMouseEvent(
5421 eContextMenu, wParam, pos, contextMenukey,
5422 contextMenukey ? MouseButton::ePrimary : MouseButton::eSecondary,
5423 MOUSE_INPUT_SOURCE());
5424 if (lParam != -1 && !result && mCustomNonClient &&
5425 mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
5426 // Blank area hit, throw up the system menu.
5427 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5428 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5429 result = true;
5431 } break;
5433 case WM_POINTERLEAVE:
5434 case WM_POINTERDOWN:
5435 case WM_POINTERUP:
5436 case WM_POINTERUPDATE:
5437 result = OnPointerEvents(msg, wParam, lParam);
5438 if (result) {
5439 DispatchPendingEvents();
5441 break;
5443 case DM_POINTERHITTEST:
5444 if (mDmOwner) {
5445 UINT contactId = GET_POINTERID_WPARAM(wParam);
5446 POINTER_INPUT_TYPE pointerType;
5447 if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
5448 pointerType == PT_TOUCHPAD) {
5449 mDmOwner->SetContact(contactId);
5452 break;
5454 case WM_LBUTTONDBLCLK:
5455 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5456 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5457 DispatchPendingEvents();
5458 break;
5460 case WM_MBUTTONDOWN:
5461 result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5462 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5463 DispatchPendingEvents();
5464 break;
5466 case WM_MBUTTONUP:
5467 result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5468 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5469 DispatchPendingEvents();
5470 break;
5472 case WM_MBUTTONDBLCLK:
5473 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5474 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5475 DispatchPendingEvents();
5476 break;
5478 case WM_NCMBUTTONDOWN:
5479 result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5480 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5481 DispatchPendingEvents();
5482 break;
5484 case WM_NCMBUTTONUP:
5485 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5486 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5487 DispatchPendingEvents();
5488 break;
5490 case WM_NCMBUTTONDBLCLK:
5491 result =
5492 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5493 false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5494 DispatchPendingEvents();
5495 break;
5497 case WM_RBUTTONDOWN:
5498 result =
5499 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5500 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5501 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5502 DispatchPendingEvents();
5503 break;
5505 case WM_RBUTTONUP:
5506 result =
5507 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5508 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5509 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5510 DispatchPendingEvents();
5511 break;
5513 case WM_RBUTTONDBLCLK:
5514 result =
5515 DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5516 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5517 DispatchPendingEvents();
5518 break;
5520 case WM_NCRBUTTONDOWN:
5521 result =
5522 DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5523 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5524 DispatchPendingEvents();
5525 break;
5527 case WM_NCRBUTTONUP:
5528 result =
5529 DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5530 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5531 DispatchPendingEvents();
5532 break;
5534 case WM_NCRBUTTONDBLCLK:
5535 result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5536 false, MouseButton::eSecondary,
5537 MOUSE_INPUT_SOURCE());
5538 DispatchPendingEvents();
5539 break;
5541 // Windows doesn't provide to customize the behavior of 4th nor 5th button
5542 // of mouse. If 5-button mouse works with standard mouse deriver of
5543 // Windows, users cannot disable 4th button (browser back) nor 5th button
5544 // (browser forward). We should allow to do it with our prefs since we can
5545 // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
5546 // messages are not sent to DefWindowProc.
5547 case WM_XBUTTONDOWN:
5548 case WM_XBUTTONUP:
5549 case WM_NCXBUTTONDOWN:
5550 case WM_NCXBUTTONUP:
5551 *aRetValue = TRUE;
5552 switch (GET_XBUTTON_WPARAM(wParam)) {
5553 case XBUTTON1:
5554 result = !Preferences::GetBool("mousebutton.4th.enabled", true);
5555 break;
5556 case XBUTTON2:
5557 result = !Preferences::GetBool("mousebutton.5th.enabled", true);
5558 break;
5559 default:
5560 break;
5562 break;
5564 case WM_SIZING: {
5565 if (mAspectRatio > 0) {
5566 LPRECT rect = (LPRECT)lParam;
5567 int32_t newWidth, newHeight;
5569 // The following conditions and switch statement borrow heavily from the
5570 // Chromium source code from
5571 // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
5572 if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
5573 wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
5574 newWidth = rect->right - rect->left;
5575 newHeight = newWidth / mAspectRatio;
5576 if (newHeight < mSizeConstraints.mMinSize.height) {
5577 newHeight = mSizeConstraints.mMinSize.height;
5578 newWidth = newHeight * mAspectRatio;
5579 } else if (newHeight > mSizeConstraints.mMaxSize.height) {
5580 newHeight = mSizeConstraints.mMaxSize.height;
5581 newWidth = newHeight * mAspectRatio;
5583 } else {
5584 newHeight = rect->bottom - rect->top;
5585 newWidth = newHeight * mAspectRatio;
5586 if (newWidth < mSizeConstraints.mMinSize.width) {
5587 newWidth = mSizeConstraints.mMinSize.width;
5588 newHeight = newWidth / mAspectRatio;
5589 } else if (newWidth > mSizeConstraints.mMaxSize.width) {
5590 newWidth = mSizeConstraints.mMaxSize.width;
5591 newHeight = newWidth / mAspectRatio;
5595 switch (wParam) {
5596 case WMSZ_RIGHT:
5597 case WMSZ_BOTTOM:
5598 rect->right = newWidth + rect->left;
5599 rect->bottom = rect->top + newHeight;
5600 break;
5601 case WMSZ_TOP:
5602 rect->right = newWidth + rect->left;
5603 rect->top = rect->bottom - newHeight;
5604 break;
5605 case WMSZ_LEFT:
5606 case WMSZ_TOPLEFT:
5607 rect->left = rect->right - newWidth;
5608 rect->top = rect->bottom - newHeight;
5609 break;
5610 case WMSZ_TOPRIGHT:
5611 rect->right = rect->left + newWidth;
5612 rect->top = rect->bottom - newHeight;
5613 break;
5614 case WMSZ_BOTTOMLEFT:
5615 rect->left = rect->right - newWidth;
5616 rect->bottom = rect->top + newHeight;
5617 break;
5618 case WMSZ_BOTTOMRIGHT:
5619 rect->right = rect->left + newWidth;
5620 rect->bottom = rect->top + newHeight;
5621 break;
5625 // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
5626 // resize or move event. Instead we wait for first VM_SIZING message
5627 // within a ENTERSIZEMOVE to consider this a live resize event.
5628 if (mResizeState == IN_SIZEMOVE) {
5629 mResizeState = RESIZING;
5630 NotifyLiveResizeStarted();
5632 break;
5635 case WM_MOVING:
5636 FinishLiveResizing(MOVING);
5637 if (WinUtils::IsPerMonitorDPIAware()) {
5638 // Sometimes, we appear to miss a WM_DPICHANGED message while moving
5639 // a window around. Therefore, call ChangedDPI and ResetLayout here
5640 // if it appears that the window's scaling is not what we expect.
5641 // This causes the prescontext and appshell window management code to
5642 // check the appUnitsPerDevPixel value and current widget size, and
5643 // refresh them if necessary. If nothing has changed, these calls will
5644 // return without actually triggering any extra reflow or painting.
5645 if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
5646 ChangedDPI();
5647 ResetLayout();
5648 if (mWidgetListener) {
5649 mWidgetListener->UIResolutionChanged();
5653 break;
5655 case WM_ENTERSIZEMOVE: {
5656 if (mResizeState == NOT_RESIZING) {
5657 mResizeState = IN_SIZEMOVE;
5659 break;
5662 case WM_EXITSIZEMOVE: {
5663 FinishLiveResizing(NOT_RESIZING);
5665 if (!sIsInMouseCapture) {
5666 NotifySizeMoveDone();
5669 // Windows spins a separate hidden event loop when moving a window so we
5670 // don't hear mouse events during this time and WM_EXITSIZEMOVE is fired
5671 // when the hidden event loop exits. We set mDraggingWindowWithMouse to
5672 // true in WM_NCLBUTTONDOWN when we started moving the window with the
5673 // mouse so we know that if mDraggingWindowWithMouse is true, we can send
5674 // a mouse up event.
5675 if (mDraggingWindowWithMouse) {
5676 mDraggingWindowWithMouse = false;
5677 result = DispatchMouseEvent(
5678 eMouseUp, wParam, lParam, false, MouseButton::ePrimary,
5679 MOUSE_INPUT_SOURCE(),
5680 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5683 break;
5686 case WM_DISPLAYCHANGE: {
5687 ScreenHelperWin::RefreshScreens();
5688 if (mWidgetListener) {
5689 mWidgetListener->UIResolutionChanged();
5691 break;
5694 case WM_NCLBUTTONDBLCLK:
5695 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
5696 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5697 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5698 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5699 DispatchPendingEvents();
5700 break;
5702 case WM_NCLBUTTONDOWN: {
5703 // Dispatch a custom event when this happens in the draggable region, so
5704 // that non-popup-based panels can react to it. This doesn't send an
5705 // actual mousedown event because that would break dragging or interfere
5706 // with other mousedown handling in the caption area.
5707 if (ClientMarginHitTestPoint(GET_X_LPARAM(lParam),
5708 GET_Y_LPARAM(lParam)) == HTCAPTION) {
5709 DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
5710 mDraggingWindowWithMouse = true;
5713 if (IsWindowButton(wParam) && mCustomNonClient) {
5714 DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
5715 lParamToClient(lParam), false, MouseButton::ePrimary,
5716 MOUSE_INPUT_SOURCE(), nullptr, true);
5717 DispatchPendingEvents();
5718 result = true;
5720 break;
5723 case WM_APPCOMMAND: {
5724 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5725 result = HandleAppCommandMsg(nativeMsg, aRetValue);
5726 break;
5729 // The WM_ACTIVATE event is fired when a window is raised or lowered,
5730 // and the loword of wParam specifies which. But we don't want to tell
5731 // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
5732 // events are fired. Instead, set either the sJustGotActivate or
5733 // gJustGotDeactivate flags and activate/deactivate once the focus
5734 // events arrive.
5735 case WM_ACTIVATE: {
5736 int32_t fActive = LOWORD(wParam);
5737 if (mWidgetListener) {
5738 if (WA_INACTIVE == fActive) {
5739 // when minimizing a window, the deactivation and focus events will
5740 // be fired in the reverse order. Instead, just deactivate right away.
5741 // This can also happen when a modal system dialog is opened, so check
5742 // if the last window to receive the WM_KILLFOCUS message was this one
5743 // or a child of this one.
5744 if (HIWORD(wParam) ||
5745 (mLastKillFocusWindow &&
5746 (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
5747 DispatchFocusToTopLevelWindow(false);
5748 } else {
5749 sJustGotDeactivate = true;
5751 if (mIsTopWidgetWindow) {
5752 mLastKeyboardLayout = KeyboardLayout::GetLayout();
5754 } else {
5755 StopFlashing();
5757 sJustGotActivate = true;
5758 WidgetMouseEvent event(true, eMouseActivate, this,
5759 WidgetMouseEvent::eReal);
5760 InitEvent(event);
5761 ModifierKeyState modifierKeyState;
5762 modifierKeyState.InitInputEvent(event);
5763 DispatchInputEvent(&event);
5764 if (sSwitchKeyboardLayout && mLastKeyboardLayout)
5765 ActivateKeyboardLayout(mLastKeyboardLayout, 0);
5767 #ifdef ACCESSIBILITY
5768 a11y::LazyInstantiator::ResetUiaDetectionCache();
5769 #endif
5772 } break;
5774 case WM_ACTIVATEAPP: {
5775 // Bug 1851991: Sometimes this can be called before gfxPlatform::Init
5776 // when a window is created very early. In that case we just forego
5777 // setting this and accept the GPU process might briefly run at a lower
5778 // priority.
5779 if (GPUProcessManager::Get()) {
5780 GPUProcessManager::Get()->SetAppInForeground(wParam);
5782 } break;
5784 case WM_MOUSEACTIVATE:
5785 // A popup with a parent owner should not be activated when clicked but
5786 // should still allow the mouse event to be fired, so the return value
5787 // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
5788 // just use default processing so that the window is activated.
5789 if (IsPopup() && IsOwnerForegroundWindow()) {
5790 *aRetValue = MA_NOACTIVATE;
5791 result = true;
5793 break;
5795 case WM_WINDOWPOSCHANGING: {
5796 LPWINDOWPOS info = (LPWINDOWPOS)lParam;
5797 OnWindowPosChanging(info);
5798 result = true;
5799 } break;
5801 // Workaround for race condition in explorer.exe.
5802 case MOZ_WM_FULLSCREEN_STATE_UPDATE: {
5803 TaskbarConcealer::OnAsyncStateUpdateRequest(mWnd);
5804 result = true;
5805 } break;
5807 case WM_GETMINMAXINFO: {
5808 MINMAXINFO* mmi = (MINMAXINFO*)lParam;
5809 // Set the constraints. The minimum size should also be constrained to the
5810 // default window maximum size so that it fits on screen.
5811 mmi->ptMinTrackSize.x =
5812 std::min((int32_t)mmi->ptMaxTrackSize.x,
5813 std::max((int32_t)mmi->ptMinTrackSize.x,
5814 mSizeConstraints.mMinSize.width));
5815 mmi->ptMinTrackSize.y =
5816 std::min((int32_t)mmi->ptMaxTrackSize.y,
5817 std::max((int32_t)mmi->ptMinTrackSize.y,
5818 mSizeConstraints.mMinSize.height));
5819 mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
5820 mSizeConstraints.mMaxSize.width);
5821 mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
5822 mSizeConstraints.mMaxSize.height);
5823 } break;
5825 case WM_SETFOCUS: {
5826 WndProcUrgentInvocation::Marker _marker;
5828 // If previous focused window isn't ours, it must have received the
5829 // redirected message. So, we should forget it.
5830 if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
5831 RedirectedKeyDownMessageManager::Forget();
5833 if (sJustGotActivate) {
5834 DispatchFocusToTopLevelWindow(true);
5836 TaskbarConcealer::OnFocusAcquired(this);
5837 } break;
5839 case WM_KILLFOCUS:
5840 if (sJustGotDeactivate) {
5841 DispatchFocusToTopLevelWindow(false);
5842 } else {
5843 mLastKillFocusWindow = mWnd;
5845 break;
5847 case WM_WINDOWPOSCHANGED: {
5848 WINDOWPOS* wp = (LPWINDOWPOS)lParam;
5849 OnWindowPosChanged(wp);
5850 TaskbarConcealer::OnWindowPosChanged(this);
5851 result = true;
5852 } break;
5854 case WM_INPUTLANGCHANGEREQUEST:
5855 *aRetValue = TRUE;
5856 result = false;
5857 break;
5859 case WM_INPUTLANGCHANGE:
5860 KeyboardLayout::GetInstance()->OnLayoutChange(
5861 reinterpret_cast<HKL>(lParam));
5862 nsBidiKeyboard::OnLayoutChange();
5863 result = false; // always pass to child window
5864 break;
5866 case WM_DESTROYCLIPBOARD: {
5867 nsIClipboard* clipboard;
5868 nsresult rv = CallGetService(kCClipboardCID, &clipboard);
5869 if (NS_SUCCEEDED(rv)) {
5870 clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
5871 NS_RELEASE(clipboard);
5873 } break;
5875 #ifdef ACCESSIBILITY
5876 case WM_GETOBJECT: {
5877 *aRetValue = 0;
5878 // Do explicit casting to make it working on 64bit systems (see bug 649236
5879 // for details).
5880 int32_t objId = static_cast<DWORD>(lParam);
5881 if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
5882 RefPtr<IAccessible> root(
5883 a11y::LazyInstantiator::GetRootAccessible(mWnd));
5884 if (root) {
5885 *aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
5886 a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
5887 result = true;
5890 } break;
5891 #endif
5893 case WM_SYSCOMMAND: {
5894 WPARAM const filteredWParam = (wParam & 0xFFF0);
5896 // SC_CLOSE may trigger a synchronous confirmation prompt. If we're in the
5897 // middle of something important, put off responding to it.
5898 if (filteredWParam == SC_CLOSE && WndProcUrgentInvocation::IsActive()) {
5899 ::PostMessageW(mWnd, msg, wParam, lParam);
5900 result = true;
5901 break;
5904 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
5905 filteredWParam == SC_RESTORE &&
5906 GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
5907 mFrameState->EnsureFullscreenMode(false);
5908 result = true;
5911 // Handle the system menu manually when we're in full screen mode
5912 // so we can set the appropriate options.
5913 if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE &&
5914 mFrameState->GetSizeMode() == nsSizeMode_Fullscreen) {
5915 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5916 MOZ_SYSCONTEXT_X_POS, MOZ_SYSCONTEXT_Y_POS);
5917 result = true;
5919 } break;
5921 case WM_DPICHANGED: {
5922 LPRECT rect = (LPRECT)lParam;
5923 OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
5924 rect->bottom - rect->top);
5925 break;
5928 /* Gesture support events */
5929 case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
5930 // According to MS samples, this must be handled to enable
5931 // rotational support in multi-touch drivers.
5932 result = true;
5933 *aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
5934 break;
5936 case WM_TOUCH:
5937 result = OnTouch(wParam, lParam);
5938 if (result) {
5939 *aRetValue = 0;
5941 break;
5943 case WM_GESTURE:
5944 result = OnGesture(wParam, lParam);
5945 break;
5947 case WM_GESTURENOTIFY: {
5948 if (mWindowType != WindowType::Invisible) {
5949 // A GestureNotify event is dispatched to decide which single-finger
5950 // panning direction should be active (including none) and if pan
5951 // feedback should be displayed. Java and plugin windows can make their
5952 // own calls.
5954 GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
5955 nsPointWin touchPoint;
5956 touchPoint = gestureinfo->ptsLocation;
5957 touchPoint.ScreenToClient(mWnd);
5958 WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
5959 gestureNotifyEvent.mRefPoint =
5960 LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
5961 nsEventStatus status;
5962 DispatchEvent(&gestureNotifyEvent, status);
5963 mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
5964 if (!mTouchWindow)
5965 mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
5967 result = false; // should always bubble to DefWindowProc
5968 } break;
5970 case WM_CLEAR: {
5971 WidgetContentCommandEvent command(true, eContentCommandDelete, this);
5972 DispatchWindowEvent(command);
5973 result = true;
5974 } break;
5976 case WM_CUT: {
5977 WidgetContentCommandEvent command(true, eContentCommandCut, this);
5978 DispatchWindowEvent(command);
5979 result = true;
5980 } break;
5982 case WM_COPY: {
5983 WidgetContentCommandEvent command(true, eContentCommandCopy, this);
5984 DispatchWindowEvent(command);
5985 result = true;
5986 } break;
5988 case WM_PASTE: {
5989 WidgetContentCommandEvent command(true, eContentCommandPaste, this);
5990 DispatchWindowEvent(command);
5991 result = true;
5992 } break;
5994 case EM_UNDO: {
5995 WidgetContentCommandEvent command(true, eContentCommandUndo, this);
5996 DispatchWindowEvent(command);
5997 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
5998 result = true;
5999 } break;
6001 case EM_REDO: {
6002 WidgetContentCommandEvent command(true, eContentCommandRedo, this);
6003 DispatchWindowEvent(command);
6004 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6005 result = true;
6006 } break;
6008 case EM_CANPASTE: {
6009 // Support EM_CANPASTE message only when wParam isn't specified or
6010 // is plain text format.
6011 if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
6012 WidgetContentCommandEvent command(true, eContentCommandPaste, this,
6013 true);
6014 DispatchWindowEvent(command);
6015 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6016 result = true;
6018 } break;
6020 case EM_CANUNDO: {
6021 WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
6022 DispatchWindowEvent(command);
6023 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6024 result = true;
6025 } break;
6027 case EM_CANREDO: {
6028 WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
6029 DispatchWindowEvent(command);
6030 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6031 result = true;
6032 } break;
6034 case MOZ_WM_SKEWFIX: {
6035 TimeStamp skewStamp;
6036 if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
6037 &skewStamp)) {
6038 TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
6039 skewStamp);
6041 } break;
6043 default: {
6044 if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
6045 SetHasTaskbarIconBeenCreated();
6047 } break;
6050 //*aRetValue = result;
6051 if (mWnd) {
6052 return result;
6053 } else {
6054 // Events which caused mWnd destruction and aren't consumed
6055 // will crash during the Windows default processing.
6056 return true;
6060 void nsWindow::FinishLiveResizing(ResizeState aNewState) {
6061 if (mResizeState == RESIZING) {
6062 NotifyLiveResizeStopped();
6064 mResizeState = aNewState;
6065 ForcePresent();
6068 /**************************************************************
6070 * SECTION: Event processing helpers
6072 * Special processing for certain event types and
6073 * synthesized events.
6075 **************************************************************/
6077 LayoutDeviceIntMargin nsWindow::NonClientSizeMargin(
6078 const LayoutDeviceIntMargin& aNonClientOffset) const {
6079 return LayoutDeviceIntMargin(mCaptionHeight - aNonClientOffset.top,
6080 mHorResizeMargin - aNonClientOffset.right,
6081 mVertResizeMargin - aNonClientOffset.bottom,
6082 mHorResizeMargin - aNonClientOffset.left);
6085 int32_t nsWindow::ClientMarginHitTestPoint(int32_t aX, int32_t aY) {
6086 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
6087 if (sizeMode == nsSizeMode_Minimized || sizeMode == nsSizeMode_Fullscreen) {
6088 return HTCLIENT;
6091 // Calculations are done in screen coords
6092 const LayoutDeviceIntRect winRect = GetScreenBounds();
6093 const LayoutDeviceIntPoint point(aX, aY);
6095 // hit return constants:
6096 // HTBORDER - non-resizable border
6097 // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
6098 // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
6099 // HTTOPLEFT, HTTOPRIGHT - resizable corner
6100 // HTCAPTION - general title bar area
6101 // HTCLIENT - area considered the client
6102 // HTCLOSE - hovering over the close button
6103 // HTMAXBUTTON - maximize button
6104 // HTMINBUTTON - minimize button
6106 int32_t testResult = HTCLIENT;
6107 const bool isResizable =
6108 sizeMode != nsSizeMode_Maximized &&
6109 (mBorderStyle &
6110 (BorderStyle::All | BorderStyle::ResizeH | BorderStyle::Default));
6112 LayoutDeviceIntMargin nonClientSizeMargin = NonClientSizeMargin();
6114 // Ensure being accessible to borders of window. Even if contents are in
6115 // this area, the area must behave as border.
6116 nonClientSizeMargin.EnsureAtLeast(
6117 LayoutDeviceIntMargin(kResizableBorderMinSize, kResizableBorderMinSize,
6118 kResizableBorderMinSize, kResizableBorderMinSize));
6120 LayoutDeviceIntRect clientRect = winRect;
6121 clientRect.Deflate(nonClientSizeMargin);
6123 const bool allowContentOverride =
6124 sizeMode == nsSizeMode_Maximized || clientRect.Contains(point);
6126 // The border size. If there is no content under mouse cursor, the border
6127 // size should be larger than the values in system settings. Otherwise,
6128 // contents under the mouse cursor should be able to override the behavior.
6129 // E.g., user must expect that Firefox button always opens the popup menu
6130 // even when the user clicks on the above edge of it.
6131 LayoutDeviceIntMargin borderSize = nonClientSizeMargin;
6132 borderSize.EnsureAtLeast(
6133 LayoutDeviceIntMargin(mVertResizeMargin, mHorResizeMargin,
6134 mVertResizeMargin, mHorResizeMargin));
6136 bool top = false;
6137 bool bottom = false;
6138 bool left = false;
6139 bool right = false;
6141 if (point.y >= winRect.y && point.y < winRect.y + borderSize.top) {
6142 top = true;
6143 } else if (point.y <= winRect.YMost() &&
6144 point.y > winRect.YMost() - borderSize.bottom) {
6145 bottom = true;
6148 // (the 2x case here doubles the resize area for corners)
6149 int multiplier = (top || bottom) ? 2 : 1;
6150 if (point.x >= winRect.x &&
6151 point.x < winRect.x + (multiplier * borderSize.left)) {
6152 left = true;
6153 } else if (point.x <= winRect.XMost() &&
6154 point.x > winRect.XMost() - (multiplier * borderSize.right)) {
6155 right = true;
6158 bool inResizeRegion = false;
6159 if (isResizable) {
6160 if (top) {
6161 testResult = HTTOP;
6162 if (left) {
6163 testResult = HTTOPLEFT;
6164 } else if (right) {
6165 testResult = HTTOPRIGHT;
6167 } else if (bottom) {
6168 testResult = HTBOTTOM;
6169 if (left) {
6170 testResult = HTBOTTOMLEFT;
6171 } else if (right) {
6172 testResult = HTBOTTOMRIGHT;
6174 } else {
6175 if (left) {
6176 testResult = HTLEFT;
6178 if (right) {
6179 testResult = HTRIGHT;
6182 inResizeRegion = (testResult != HTCLIENT);
6183 } else {
6184 if (top) {
6185 testResult = HTCAPTION;
6186 } else if (bottom || left || right) {
6187 testResult = HTBORDER;
6191 if (!sIsInMouseCapture && allowContentOverride) {
6193 POINT pt = {aX, aY};
6194 ::ScreenToClient(mWnd, &pt);
6196 if (pt.x == mCachedHitTestPoint.x.value &&
6197 pt.y == mCachedHitTestPoint.y.value &&
6198 TimeStamp::Now() - mCachedHitTestTime <
6199 TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
6200 return mCachedHitTestResult;
6203 mCachedHitTestPoint = {pt.x, pt.y};
6204 mCachedHitTestTime = TimeStamp::Now();
6207 auto pt = mCachedHitTestPoint;
6209 if (mWindowBtnRect[WindowButtonType::Minimize].Contains(pt)) {
6210 testResult = HTMINBUTTON;
6211 } else if (mWindowBtnRect[WindowButtonType::Maximize].Contains(pt)) {
6212 #ifdef ACCESSIBILITY
6213 a11y::Compatibility::SuppressA11yForSnapLayouts();
6214 #endif
6215 testResult = HTMAXBUTTON;
6216 } else if (mWindowBtnRect[WindowButtonType::Close].Contains(pt)) {
6217 testResult = HTCLOSE;
6218 } else if (!inResizeRegion) {
6219 // If we're in the resize region, avoid overriding that with either a
6220 // drag or a client result; resize takes priority over either (but not
6221 // over the window controls, which is why we check this after those).
6222 if (mDraggableRegion.Contains(pt)) {
6223 testResult = HTCAPTION;
6224 } else {
6225 testResult = HTCLIENT;
6229 mCachedHitTestResult = testResult;
6232 return testResult;
6235 bool nsWindow::IsSimulatedClientArea(int32_t screenX, int32_t screenY) {
6236 int32_t testResult = ClientMarginHitTestPoint(screenX, screenY);
6237 return testResult == HTCAPTION || IsWindowButton(testResult);
6240 bool nsWindow::IsWindowButton(int32_t hitTestResult) {
6241 return hitTestResult == HTMINBUTTON || hitTestResult == HTMAXBUTTON ||
6242 hitTestResult == HTCLOSE;
6245 TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
6246 CurrentWindowsTimeGetter getCurrentTime(mWnd);
6247 return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
6250 void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
6251 // Retain the previous mode that was notified to observers
6252 static bool sWasSleepMode = false;
6254 // Only notify observers if mode changed
6255 if (aIsSleepMode == sWasSleepMode) return;
6257 sWasSleepMode = aIsSleepMode;
6259 nsCOMPtr<nsIObserverService> observerService =
6260 mozilla::services::GetObserverService();
6261 if (observerService)
6262 observerService->NotifyObservers(nullptr,
6263 aIsSleepMode
6264 ? NS_WIDGET_SLEEP_OBSERVER_TOPIC
6265 : NS_WIDGET_WAKE_OBSERVER_TOPIC,
6266 nullptr);
6269 LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
6270 if (IMEHandler::IsComposingOn(this)) {
6271 IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
6273 // These must be checked here too as a lone WM_CHAR could be received
6274 // if a child window didn't handle it (for example Alt+Space in a content
6275 // window)
6276 ModifierKeyState modKeyState;
6277 NativeKey nativeKey(this, aMsg, modKeyState);
6278 return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
6281 LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
6282 ModifierKeyState modKeyState;
6283 NativeKey nativeKey(this, aMsg, modKeyState);
6284 bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
6285 if (aMsg.wParam == VK_F10) {
6286 // Bug 1382199: Windows default behavior will trigger the System menu bar
6287 // when F10 is released. Among other things, this causes the System menu bar
6288 // to appear when a web page overrides the contextmenu event. We *never*
6289 // want this default behavior, so eat this key (never pass it to Windows).
6290 return true;
6292 return result;
6295 LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
6296 bool* aEventDispatched) {
6297 // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
6298 // must clean up the redirected message information itself. For more
6299 // information, see above comment of
6300 // RedirectedKeyDownMessageManager::AutoFlusher class definition in
6301 // KeyboardLayout.h.
6302 RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
6304 ModifierKeyState modKeyState;
6306 NativeKey nativeKey(this, aMsg, modKeyState);
6307 LRESULT result =
6308 static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
6309 // HandleKeyDownMessage cleaned up the redirected message information
6310 // itself, so, we should do nothing.
6311 redirectedMsgFlusher.Cancel();
6313 if (aMsg.wParam == VK_MENU ||
6314 (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
6315 // We need to let Windows handle this keypress,
6316 // by returning false, if there's a native menu
6317 // bar somewhere in our containing window hierarchy.
6318 // Otherwise we handle the keypress and don't pass
6319 // it on to Windows, by returning true.
6320 bool hasNativeMenu = false;
6321 HWND hWnd = mWnd;
6322 while (hWnd) {
6323 if (::GetMenu(hWnd)) {
6324 hasNativeMenu = true;
6325 break;
6327 hWnd = ::GetParent(hWnd);
6329 result = !hasNativeMenu;
6332 return result;
6335 nsresult nsWindow::SynthesizeNativeKeyEvent(
6336 int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
6337 uint32_t aModifierFlags, const nsAString& aCharacters,
6338 const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
6339 AutoObserverNotifier notifier(aObserver, "keyevent");
6341 KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
6342 return keyboardLayout->SynthesizeNativeKeyEvent(
6343 this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
6344 aUnmodifiedCharacters);
6347 nsresult nsWindow::SynthesizeNativeMouseEvent(
6348 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
6349 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
6350 nsIObserver* aObserver) {
6351 AutoObserverNotifier notifier(aObserver, "mouseevent");
6353 INPUT input;
6354 memset(&input, 0, sizeof(input));
6356 // TODO (bug 1693240):
6357 // Now, we synthesize native mouse events asynchronously since we want to
6358 // synthesize the event on the front window at the point. However, Windows
6359 // does not provide a way to set modifier only while a mouse message is
6360 // being handled, and MOUSEEVENTF_MOVE may be coalesced by Windows. So, we
6361 // need a trick for handling it.
6363 switch (aNativeMessage) {
6364 case NativeMouseMessage::Move:
6365 input.mi.dwFlags = MOUSEEVENTF_MOVE;
6366 // Reset sLastMouseMovePoint so that even if we're moving the mouse
6367 // to the position it's already at, we still dispatch a mousemove
6368 // event, because the callers of this function expect that.
6369 sLastMouseMovePoint = {0};
6370 break;
6371 case NativeMouseMessage::ButtonDown:
6372 case NativeMouseMessage::ButtonUp: {
6373 const bool isDown = aNativeMessage == NativeMouseMessage::ButtonDown;
6374 switch (aButton) {
6375 case MouseButton::ePrimary:
6376 input.mi.dwFlags = isDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
6377 break;
6378 case MouseButton::eMiddle:
6379 input.mi.dwFlags =
6380 isDown ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
6381 break;
6382 case MouseButton::eSecondary:
6383 input.mi.dwFlags =
6384 isDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
6385 break;
6386 case MouseButton::eX1:
6387 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6388 input.mi.mouseData = XBUTTON1;
6389 break;
6390 case MouseButton::eX2:
6391 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6392 input.mi.mouseData = XBUTTON2;
6393 break;
6394 default:
6395 return NS_ERROR_INVALID_ARG;
6397 break;
6399 case NativeMouseMessage::EnterWindow:
6400 case NativeMouseMessage::LeaveWindow:
6401 MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Windows");
6402 return NS_ERROR_INVALID_ARG;
6405 input.type = INPUT_MOUSE;
6406 ::SetCursorPos(aPoint.x, aPoint.y);
6407 ::SendInput(1, &input, sizeof(INPUT));
6409 return NS_OK;
6412 nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
6413 LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
6414 double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
6415 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
6416 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
6417 return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
6418 this, aPoint, aNativeMessage,
6419 (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
6420 ? static_cast<int32_t>(aDeltaY)
6421 : static_cast<int32_t>(aDeltaX),
6422 aModifierFlags, aAdditionalFlags);
6425 nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
6426 LayoutDeviceIntPoint aPoint,
6427 double aDeltaX, double aDeltaY,
6428 int32_t aModifierFlags,
6429 nsIObserver* aObserver) {
6430 AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
6431 DirectManipulationOwner::SynthesizeNativeTouchpadPan(
6432 this, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
6433 return NS_OK;
6436 static void MaybeLogPosChanged(HWND aWnd, WINDOWPOS* wp) {
6437 #ifdef WINSTATE_DEBUG_OUTPUT
6438 if (aWnd == WinUtils::GetTopLevelHWND(aWnd)) {
6439 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
6440 } else {
6441 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
6443 MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
6444 if (wp->flags & SWP_FRAMECHANGED) {
6445 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
6447 if (wp->flags & SWP_SHOWWINDOW) {
6448 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
6450 if (wp->flags & SWP_NOSIZE) {
6451 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
6453 if (wp->flags & SWP_HIDEWINDOW) {
6454 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
6456 if (wp->flags & SWP_NOZORDER) {
6457 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
6459 if (wp->flags & SWP_NOACTIVATE) {
6460 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
6462 MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
6463 #endif
6466 /**************************************************************
6468 * SECTION: OnXXX message handlers
6470 * For message handlers that need to be broken out or
6471 * implemented in specific platform code.
6473 **************************************************************/
6475 void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
6476 if (!wp) {
6477 return;
6480 MaybeLogPosChanged(mWnd, wp);
6482 // Handle window size mode changes
6483 if (wp->flags & SWP_FRAMECHANGED) {
6484 // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
6485 // windows when fullscreen games disable desktop composition. If we're
6486 // minimized and not being activated, ignore the event and let windows
6487 // handle it.
6488 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized &&
6489 (wp->flags & SWP_NOACTIVATE)) {
6490 return;
6493 mFrameState->OnFrameChanged();
6495 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
6496 // Skip window size change events below on minimization.
6497 return;
6501 // Notify visibility change when window is activated.
6502 if (!(wp->flags & SWP_NOACTIVATE) && NeedsToTrackWindowOcclusionState()) {
6503 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
6504 this, mFrameState->GetSizeMode() != nsSizeMode_Minimized);
6507 // Handle window position changes
6508 if (!(wp->flags & SWP_NOMOVE)) {
6509 mBounds.MoveTo(wp->x, wp->y);
6510 NotifyWindowMoved(wp->x, wp->y);
6513 // Handle window size changes
6514 if (!(wp->flags & SWP_NOSIZE)) {
6515 RECT r;
6516 int32_t newWidth, newHeight;
6518 ::GetWindowRect(mWnd, &r);
6520 newWidth = r.right - r.left;
6521 newHeight = r.bottom - r.top;
6523 if (newWidth > mLastSize.width) {
6524 RECT drect;
6526 // getting wider
6527 drect.left = wp->x + mLastSize.width;
6528 drect.top = wp->y;
6529 drect.right = drect.left + (newWidth - mLastSize.width);
6530 drect.bottom = drect.top + newHeight;
6532 ::RedrawWindow(mWnd, &drect, nullptr,
6533 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6534 RDW_ERASENOW | RDW_ALLCHILDREN);
6536 if (newHeight > mLastSize.height) {
6537 RECT drect;
6539 // getting taller
6540 drect.left = wp->x;
6541 drect.top = wp->y + mLastSize.height;
6542 drect.right = drect.left + newWidth;
6543 drect.bottom = drect.top + (newHeight - mLastSize.height);
6545 ::RedrawWindow(mWnd, &drect, nullptr,
6546 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6547 RDW_ERASENOW | RDW_ALLCHILDREN);
6550 mBounds.SizeTo(newWidth, newHeight);
6551 mLastSize.width = newWidth;
6552 mLastSize.height = newHeight;
6554 #ifdef WINSTATE_DEBUG_OUTPUT
6555 MOZ_LOG(gWindowsLog, LogLevel::Info,
6556 ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
6557 newHeight));
6558 #endif
6560 if (mAspectRatio > 0) {
6561 // It's possible (via Windows Aero Snap) that the size of the window
6562 // has changed such that it violates the aspect ratio constraint. If so,
6563 // queue up an event to enforce the aspect ratio constraint and repaint.
6564 // When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
6565 float newAspectRatio = (float)newWidth / newHeight;
6566 if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
6567 // Hold a reference to self alive and pass it into the lambda to make
6568 // sure this nsIWidget stays alive long enough to run this function.
6569 nsCOMPtr<nsIWidget> self(this);
6570 NS_DispatchToMainThread(NS_NewRunnableFunction(
6571 "EnforceAspectRatio", [self, this, newWidth]() -> void {
6572 if (mWnd) {
6573 Resize(newWidth, newWidth / mAspectRatio, true);
6575 }));
6579 // If a maximized window is resized, recalculate the non-client margins.
6580 if (mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
6581 if (UpdateNonClientMargins(true)) {
6582 // gecko resize event already sent by UpdateNonClientMargins.
6583 return;
6588 // Notify the widget listener for size change of client area for gecko
6589 // events. This needs to be done when either window size is changed,
6590 // or window frame is changed. They may not happen together.
6591 // However, we don't invoke that for popup when window frame changes,
6592 // because popups may trigger frame change before size change via
6593 // {Set,Clear}ThemeRegion they invoke in Resize. That would make the
6594 // code below call OnResize with a wrong client size first, which can
6595 // lead to flickerling for some popups.
6596 if (!(wp->flags & SWP_NOSIZE) ||
6597 ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
6598 RECT r;
6599 LayoutDeviceIntSize clientSize;
6600 if (::GetClientRect(mWnd, &r)) {
6601 clientSize = WinUtils::ToIntRect(r).Size();
6602 } else {
6603 clientSize = mBounds.Size();
6605 // Send a gecko resize event
6606 OnResize(clientSize);
6610 void nsWindow::OnWindowPosChanging(WINDOWPOS* info) {
6611 // Update non-client margins if the frame size is changing, and let the
6612 // browser know we are changing size modes, so alternative css can kick in.
6613 // If we're going into fullscreen mode, ignore this, since it'll reset
6614 // margins to normal mode.
6615 if (info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) {
6616 mFrameState->OnFrameChanging();
6619 // Force fullscreen. This works around a bug in Windows 10 1809 where
6620 // using fullscreen when a window is "snapped" causes a spurious resize
6621 // smaller than the full screen, see bug 1482920.
6622 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
6623 !(info->flags & SWP_NOMOVE) && !(info->flags & SWP_NOSIZE)) {
6624 nsCOMPtr<nsIScreenManager> screenmgr =
6625 do_GetService(sScreenManagerContractID);
6626 if (screenmgr) {
6627 LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
6628 DesktopIntRect deskBounds =
6629 RoundedToInt(bounds / GetDesktopToDeviceScale());
6630 nsCOMPtr<nsIScreen> screen;
6631 screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
6632 deskBounds.Width(), deskBounds.Height(),
6633 getter_AddRefs(screen));
6635 if (screen) {
6636 auto rect = screen->GetRect();
6637 info->x = rect.x;
6638 info->y = rect.y;
6639 info->cx = rect.width;
6640 info->cy = rect.height;
6645 // enforce local z-order rules
6646 if (!(info->flags & SWP_NOZORDER)) {
6647 HWND hwndAfter = info->hwndInsertAfter;
6649 nsWindow* aboveWindow = 0;
6650 nsWindowZ placement;
6652 if (hwndAfter == HWND_BOTTOM)
6653 placement = nsWindowZBottom;
6654 else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST ||
6655 hwndAfter == HWND_NOTOPMOST)
6656 placement = nsWindowZTop;
6657 else {
6658 placement = nsWindowZRelative;
6659 aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter);
6662 if (mWidgetListener) {
6663 nsCOMPtr<nsIWidget> actualBelow = nullptr;
6664 if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow,
6665 getter_AddRefs(actualBelow))) {
6666 if (placement == nsWindowZBottom)
6667 info->hwndInsertAfter = HWND_BOTTOM;
6668 else if (placement == nsWindowZTop)
6669 info->hwndInsertAfter = HWND_TOP;
6670 else {
6671 info->hwndInsertAfter =
6672 (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW);
6677 // prevent rude external programs from making hidden window visible
6678 if (mWindowType == WindowType::Invisible) info->flags &= ~SWP_SHOWWINDOW;
6680 // When waking from sleep or switching out of tablet mode, Windows 10
6681 // Version 1809 will reopen popup windows that should be hidden. Detect
6682 // this case and refuse to show the window.
6683 static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
6684 if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
6685 mWindowType == WindowType::Popup && mWidgetListener &&
6686 mWidgetListener->ShouldNotBeVisible()) {
6687 info->flags &= ~SWP_SHOWWINDOW;
6691 void nsWindow::UserActivity() {
6692 // Check if we have the idle service, if not we try to get it.
6693 if (!mIdleService) {
6694 mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
6697 // Check that we now have the idle service.
6698 if (mIdleService) {
6699 mIdleService->ResetIdleTimeOut(0);
6703 // Helper function for TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT,
6704 // uint32_t).
6705 static bool TouchDeviceNeedsPanGestureConversion(HANDLE aSource) {
6706 std::string deviceName;
6707 UINT dataSize = 0;
6708 // The first call just queries how long the name string will be.
6709 GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, nullptr, &dataSize);
6710 if (!dataSize || dataSize > 0x10000) {
6711 return false;
6713 deviceName.resize(dataSize);
6714 // The second call actually populates the string.
6715 UINT result = GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, &deviceName[0],
6716 &dataSize);
6717 if (result == UINT_MAX) {
6718 return false;
6720 // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
6721 // needs to be escaped with another one.
6722 std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
6723 // For some reason, the dataSize returned by the first call is double the
6724 // actual length of the device name (as if it were returning the size of a
6725 // wide-character string in bytes) even though we are using the narrow
6726 // version of the API. For the comparison against the expected device name
6727 // to pass, we truncate the buffer to be no longer tha the expected device
6728 // name.
6729 if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
6730 return false;
6733 RID_DEVICE_INFO deviceInfo;
6734 deviceInfo.cbSize = sizeof(deviceInfo);
6735 dataSize = sizeof(deviceInfo);
6736 result =
6737 GetRawInputDeviceInfoA(aSource, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
6738 if (result == UINT_MAX) {
6739 return false;
6741 // The device identifiers that we check for here come from bug 1355162
6742 // comment 1 (see also bug 1511901 comment 35).
6743 return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
6744 deviceInfo.hid.dwProductId == 0 &&
6745 deviceInfo.hid.dwVersionNumber == 1 &&
6746 deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
6749 // Determine if the touch device that originated |aOSEvent| needs to have
6750 // touch events representing a two-finger gesture converted to pan
6751 // gesture events.
6752 // We only do this for touch devices with a specific name and identifiers.
6753 static bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
6754 uint32_t aTouchCount) {
6755 if (!StaticPrefs::apz_windows_check_for_pan_gesture_conversion()) {
6756 return false;
6758 if (aTouchCount == 0) {
6759 return false;
6761 HANDLE source = aOSEvent[0].hSource;
6763 // Cache the result of this computation for each touch device.
6764 // Touch devices are identified by the HANDLE stored in the hSource
6765 // field of TOUCHINPUT.
6766 static std::map<HANDLE, bool> sResultCache;
6767 auto [iter, inserted] = sResultCache.emplace(source, false);
6768 if (inserted) {
6769 iter->second = TouchDeviceNeedsPanGestureConversion(source);
6771 return iter->second;
6774 Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
6775 const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
6776 // Checks if the touch device that originated the touch event is one
6777 // for which we want to convert the touch events to pang gesture events.
6778 bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
6779 aOSEvent, aTouchInput.mTouches.Length());
6780 if (!shouldConvert) {
6781 return Nothing();
6784 // Only two-finger gestures need conversion.
6785 if (aTouchInput.mTouches.Length() != 2) {
6786 return Nothing();
6789 PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
6790 if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
6791 eventType = PanGestureInput::PANGESTURE_START;
6792 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
6793 eventType = PanGestureInput::PANGESTURE_END;
6794 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
6795 eventType = PanGestureInput::PANGESTURE_CANCELLED;
6798 // Use the midpoint of the two touches as the start point of the pan gesture.
6799 ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
6800 aTouchInput.mTouches[1].mScreenPoint) /
6802 // To compute the displacement of the pan gesture, we keep track of the
6803 // location of the previous event.
6804 ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
6805 ? ScreenPoint(0, 0)
6806 : (focusPoint - mLastPanGestureFocus);
6807 mLastPanGestureFocus = focusPoint;
6809 // We need to negate the displacement because for a touch event, moving the
6810 // fingers down results in scrolling up, but for a touchpad gesture, we want
6811 // moving the fingers down to result in scrolling down.
6812 PanGestureInput result(eventType, aTouchInput.mTimeStamp, focusPoint,
6813 -displacement, aTouchInput.modifiers);
6814 result.mSimulateMomentum = true;
6816 return Some(result);
6819 // Dispatch an event that originated as an OS touch event.
6820 // Usually, we want to dispatch it as a touch event, but some touchpads
6821 // produce touch events for two-finger scrolling, which need to be converted
6822 // to pan gesture events for correct behaviour.
6823 void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
6824 PTOUCHINPUT aOSEvent) {
6825 if (Maybe<PanGestureInput> panInput =
6826 ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
6827 DispatchPanGestureInput(*panInput);
6828 return;
6831 DispatchTouchInput(aTouchInput);
6834 bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
6835 uint32_t cInputs = LOWORD(wParam);
6836 PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
6838 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
6839 sizeof(TOUCHINPUT))) {
6840 MultiTouchInput touchInput, touchEndInput;
6842 // Walk across the touch point array processing each contact point.
6843 for (uint32_t i = 0; i < cInputs; i++) {
6844 bool addToEvent = false, addToEndEvent = false;
6846 // N.B.: According with MS documentation
6847 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
6848 // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
6849 // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
6850 // TOUCHEVENTF_UP can be combined together.
6852 if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
6853 if (touchInput.mTimeStamp.IsNull()) {
6854 // Initialize a touch event to send.
6855 touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
6856 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6857 ModifierKeyState modifierKeyState;
6858 touchInput.modifiers = modifierKeyState.GetModifiers();
6860 // Pres shell expects this event to be a eTouchStart
6861 // if any new contact points have been added since the last event sent.
6862 if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
6863 touchInput.mType = MultiTouchInput::MULTITOUCH_START;
6865 addToEvent = true;
6867 if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
6868 // Pres shell expects removed contacts points to be delivered in a
6869 // separate eTouchEnd event containing only the contact points that were
6870 // removed.
6871 if (touchEndInput.mTimeStamp.IsNull()) {
6872 // Initialize a touch event to send.
6873 touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
6874 touchEndInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6875 ModifierKeyState modifierKeyState;
6876 touchEndInput.modifiers = modifierKeyState.GetModifiers();
6878 addToEndEvent = true;
6880 if (!addToEvent && !addToEndEvent) {
6881 // Filter out spurious Windows events we don't understand, like palm
6882 // contact.
6883 continue;
6886 // Setup the touch point we'll append to the touch event array.
6887 nsPointWin touchPoint;
6888 touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
6889 touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
6890 touchPoint.ScreenToClient(mWnd);
6892 // Initialize the touch data.
6893 SingleTouchData touchData(
6894 pInputs[i].dwID, // aIdentifier
6895 ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
6896 // The contact area info cannot be trusted even when
6897 // TOUCHINPUTMASKF_CONTACTAREA is set when the input source is pen,
6898 // which somehow violates the API docs. (bug 1710509) Ultimately the
6899 // dwFlags check will become redundant since we want to migrate to
6900 // WM_POINTER for pens. (bug 1707075)
6901 (pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA) &&
6902 !(pInputs[i].dwFlags & TOUCHEVENTF_PEN)
6903 ? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
6904 TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
6905 : ScreenSize(1, 1), // aRadius
6906 0.0f, // aRotationAngle
6907 0.0f); // aForce
6909 // Append touch data to the appropriate event.
6910 if (addToEvent) {
6911 touchInput.mTouches.AppendElement(touchData);
6913 if (addToEndEvent) {
6914 touchEndInput.mTouches.AppendElement(touchData);
6918 // Dispatch touch start and touch move event if we have one.
6919 if (!touchInput.mTimeStamp.IsNull()) {
6920 DispatchTouchOrPanGestureInput(touchInput, pInputs);
6922 // Dispatch touch end event if we have one.
6923 if (!touchEndInput.mTimeStamp.IsNull()) {
6924 DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
6928 delete[] pInputs;
6929 CloseTouchInputHandle((HTOUCHINPUT)lParam);
6930 return true;
6933 // Gesture event processing. Handles WM_GESTURE events.
6934 bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
6935 // Treatment for pan events which translate into scroll events:
6936 if (mGesture.IsPanEvent(lParam)) {
6937 if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
6938 return false; // ignore
6940 nsEventStatus status;
6942 WidgetWheelEvent wheelEvent(true, eWheel, this);
6944 ModifierKeyState modifierKeyState;
6945 modifierKeyState.InitInputEvent(wheelEvent);
6947 wheelEvent.mButton = 0;
6948 wheelEvent.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6949 wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
6951 bool endFeedback = true;
6953 if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
6954 DispatchEvent(&wheelEvent, status);
6957 if (mDisplayPanFeedback) {
6958 mGesture.UpdatePanFeedbackX(
6959 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
6960 endFeedback);
6961 mGesture.UpdatePanFeedbackY(
6962 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
6963 endFeedback);
6964 mGesture.PanFeedbackFinalize(mWnd, endFeedback);
6967 CloseGestureInfoHandle((HGESTUREINFO)lParam);
6969 return true;
6972 // Other gestures translate into simple gesture events:
6973 WidgetSimpleGestureEvent event(true, eVoidEvent, this);
6974 if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
6975 return false; // fall through to DefWndProc
6978 // Polish up and send off the new event
6979 ModifierKeyState modifierKeyState;
6980 modifierKeyState.InitInputEvent(event);
6981 event.mButton = 0;
6982 event.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6983 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
6985 nsEventStatus status;
6986 DispatchEvent(&event, status);
6987 if (status == nsEventStatus_eIgnore) {
6988 return false; // Ignored, fall through
6991 // Only close this if we process and return true.
6992 CloseGestureInfoHandle((HGESTUREINFO)lParam);
6994 return true; // Handled
6997 // WM_DESTROY event handler
6998 void nsWindow::OnDestroy() {
6999 mOnDestroyCalled = true;
7001 // If this is a toplevel window, notify the taskbar concealer to clean up any
7002 // relevant state.
7003 if (!mParent) {
7004 TaskbarConcealer::OnWindowDestroyed(mWnd);
7007 // Make sure we don't get destroyed in the process of tearing down.
7008 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
7010 // Dispatch the destroy notification.
7011 if (!mInDtor) NotifyWindowDestroyed();
7013 // Prevent the widget from sending additional events.
7014 mWidgetListener = nullptr;
7015 mAttachedWidgetListener = nullptr;
7017 DestroyDirectManipulation();
7019 if (mWnd == mLastKillFocusWindow) {
7020 mLastKillFocusWindow = nullptr;
7022 // Unregister notifications from terminal services
7023 ::WTSUnRegisterSessionNotification(mWnd);
7025 // We will stop receiving native events after dissociating from our native
7026 // window. We will also disappear from the output of WinUtils::GetNSWindowPtr
7027 // for that window.
7028 DissociateFromNativeWindow();
7030 // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
7031 // can be cleared. (It's used in tracking windows for mouse events.)
7032 if (sCurrentWindow == this) sCurrentWindow = nullptr;
7034 // Disconnects us from our parent, will call our GetParent().
7035 nsBaseWidget::Destroy();
7037 // Release references to children, device context, toolkit, and app shell.
7038 nsBaseWidget::OnDestroy();
7040 // Clear our native parent handle.
7041 // XXX Windows will take care of this in the proper order, and
7042 // SetParent(nullptr)'s remove child on the parent already took place in
7043 // nsBaseWidget's Destroy call above.
7044 // SetParent(nullptr);
7045 mParent = nullptr;
7047 // We have to destroy the native drag target before we null out our window
7048 // pointer.
7049 EnableDragDrop(false);
7051 // If we're going away and for some reason we're still the rollup widget,
7052 // rollup and turn off capture.
7053 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7054 nsCOMPtr<nsIWidget> rollupWidget;
7055 if (rollupListener) {
7056 rollupWidget = rollupListener->GetRollupWidget();
7058 if (this == rollupWidget) {
7059 rollupListener->Rollup({});
7060 CaptureRollupEvents(false);
7063 IMEHandler::OnDestroyWindow(this);
7065 // Free GDI window class objects
7066 if (mBrush) {
7067 VERIFY(::DeleteObject(mBrush));
7068 mBrush = nullptr;
7071 // Destroy any custom cursor resources.
7072 if (mCursor.IsCustom()) {
7073 SetCursor(Cursor{eCursor_standard});
7076 if (mCompositorWidgetDelegate) {
7077 mCompositorWidgetDelegate->OnDestroyWindow();
7079 mBasicLayersSurface = nullptr;
7081 // Finalize panning feedback to possibly restore window displacement
7082 mGesture.PanFeedbackFinalize(mWnd, true);
7084 // Clear the main HWND.
7085 mWnd = nullptr;
7088 // Send a resize message to the listener
7089 bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
7090 if (mCompositorWidgetDelegate &&
7091 !mCompositorWidgetDelegate->OnWindowResize(aSize)) {
7092 return false;
7095 bool result = false;
7096 if (mWidgetListener) {
7097 result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
7100 // If there is an attached view, inform it as well as the normal widget
7101 // listener.
7102 if (mAttachedWidgetListener) {
7103 return mAttachedWidgetListener->WindowResized(this, aSize.width,
7104 aSize.height);
7107 return result;
7110 void nsWindow::OnSizeModeChange() {
7111 const nsSizeMode mode = mFrameState->GetSizeMode();
7113 MOZ_LOG(gWindowsLog, LogLevel::Info,
7114 ("nsWindow::OnSizeModeChange() sizeMode %d", mode));
7116 if (NeedsToTrackWindowOcclusionState()) {
7117 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
7118 this, mode != nsSizeMode_Minimized);
7120 wr::DebugFlags flags{0};
7121 flags._0 = gfx::gfxVars::WebRenderDebugFlags();
7122 bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
7123 if (debugEnabled && mCompositorWidgetDelegate) {
7124 mCompositorWidgetDelegate->NotifyVisibilityUpdated(mode,
7125 mIsFullyOccluded);
7129 if (mCompositorWidgetDelegate) {
7130 mCompositorWidgetDelegate->OnWindowModeChange(mode);
7133 if (mWidgetListener) {
7134 mWidgetListener->SizeModeChanged(mode);
7138 bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
7140 bool nsWindow::IsPopup() { return mWindowType == WindowType::Popup; }
7142 bool nsWindow::ShouldUseOffMainThreadCompositing() {
7143 if (mWindowType == WindowType::Popup && mPopupType == PopupType::Tooltip) {
7144 return false;
7147 // Content rendering of popup is always done by child window.
7148 // See nsDocumentViewer::ShouldAttachToTopLevel().
7149 if (mWindowType == WindowType::Popup && !mIsChildWindow) {
7150 MOZ_ASSERT(!mParent);
7151 return false;
7154 return nsBaseWidget::ShouldUseOffMainThreadCompositing();
7157 void nsWindow::WindowUsesOMTC() {
7158 ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
7159 if (!style) {
7160 NS_WARNING("Could not get window class style");
7161 return;
7163 style |= CS_HREDRAW | CS_VREDRAW;
7164 DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
7165 NS_WARNING_ASSERTION(result, "Could not reset window class style");
7168 void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
7169 int32_t height) {
7170 // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
7171 // they remain tied to their original parent's resolution.
7172 if (mWindowType == WindowType::Popup) {
7173 return;
7175 if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
7176 return;
7178 mDefaultScale = -1.0; // force recomputation of scale factor
7180 if (mResizeState != RESIZING &&
7181 mFrameState->GetSizeMode() == nsSizeMode_Normal) {
7182 // Limit the position (if not in the middle of a drag-move) & size,
7183 // if it would overflow the destination screen
7184 nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID);
7185 if (sm) {
7186 nsCOMPtr<nsIScreen> screen;
7187 sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
7188 if (screen) {
7189 int32_t availLeft, availTop, availWidth, availHeight;
7190 screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
7191 if (mResizeState != MOVING) {
7192 x = std::max(x, availLeft);
7193 y = std::max(y, availTop);
7195 width = std::min(width, availWidth);
7196 height = std::min(height, availHeight);
7200 Resize(x, y, width, height, true);
7202 UpdateNonClientMargins();
7203 ChangedDPI();
7204 ResetLayout();
7207 // Callback to generate OnCloakChanged pseudo-events.
7208 /* static */
7209 void nsWindow::OnCloakEvent(HWND aWnd, bool aCloaked) {
7210 MOZ_ASSERT(NS_IsMainThread());
7212 const char* const kEventName = aCloaked ? "CLOAKED" : "UNCLOAKED";
7213 nsWindow* pWin = WinUtils::GetNSWindowPtr(aWnd);
7214 if (!pWin) {
7215 MOZ_LOG(
7216 sCloakingLog, LogLevel::Debug,
7217 ("Received %s event for HWND %p (not an nsWindow)", kEventName, aWnd));
7218 return;
7221 const char* const kWasCloakedStr = pWin->mIsCloaked ? "cloaked" : "uncloaked";
7222 if (mozilla::IsCloaked(aWnd) == pWin->mIsCloaked) {
7223 MOZ_LOG(sCloakingLog, LogLevel::Debug,
7224 ("Received redundant %s event for %s HWND %p; discarding",
7225 kEventName, kWasCloakedStr, aWnd));
7226 return;
7229 MOZ_LOG(
7230 sCloakingLog, LogLevel::Info,
7231 ("Received %s event for %s HWND %p", kEventName, kWasCloakedStr, aWnd));
7233 // Cloaking events like the one we've just received are sent asynchronously.
7234 // Rather than process them one-by-one, we jump the gun a bit and perform
7235 // updates on all newly cloaked/uncloaked nsWindows at once. This also lets us
7236 // batch operations that consider more than one window's state.
7237 struct Item {
7238 nsWindow* win;
7239 bool nowCloaked;
7241 nsTArray<Item> changedWindows;
7243 mozilla::EnumerateThreadWindows([&](HWND hwnd) {
7244 nsWindow* pWin = WinUtils::GetNSWindowPtr(hwnd);
7245 if (!pWin) {
7246 return;
7249 const bool isCloaked = mozilla::IsCloaked(hwnd);
7250 if (isCloaked != pWin->mIsCloaked) {
7251 changedWindows.AppendElement(Item{pWin, isCloaked});
7255 if (changedWindows.IsEmpty()) {
7256 return;
7259 for (const Item& item : changedWindows) {
7260 item.win->OnCloakChanged(item.nowCloaked);
7263 nsWindow::TaskbarConcealer::OnCloakChanged();
7266 void nsWindow::OnCloakChanged(bool aCloaked) {
7267 MOZ_LOG(sCloakingLog, LogLevel::Info,
7268 ("Calling OnCloakChanged(): HWND %p, aCloaked %s", mWnd,
7269 aCloaked ? "true" : "false"));
7270 mIsCloaked = aCloaked;
7273 /**************************************************************
7274 **************************************************************
7276 ** BLOCK: IME management and accessibility
7278 ** Handles managing IME input and accessibility.
7280 **************************************************************
7281 **************************************************************/
7283 void nsWindow::SetInputContext(const InputContext& aContext,
7284 const InputContextAction& aAction) {
7285 InputContext newInputContext = aContext;
7286 IMEHandler::SetInputContext(this, newInputContext, aAction);
7287 mInputContext = newInputContext;
7290 InputContext nsWindow::GetInputContext() {
7291 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7292 if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
7293 mInputContext.mIMEState.mOpen = IMEState::OPEN;
7294 } else {
7295 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7297 return mInputContext;
7300 TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
7301 return IMEHandler::GetNativeTextEventDispatcherListener();
7304 #ifdef ACCESSIBILITY
7305 # ifdef DEBUG
7306 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
7307 if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
7308 printf( \
7309 "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
7310 "%p,\n", \
7311 aHwnd, ::GetParent(aHwnd), aWnd); \
7312 printf(" acc: %p", aAcc); \
7313 if (aAcc) { \
7314 nsAutoString name; \
7315 aAcc->Name(name); \
7316 printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
7318 printf("\n }\n"); \
7321 # else
7322 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
7323 # endif
7325 a11y::LocalAccessible* nsWindow::GetAccessible() {
7326 // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
7327 if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
7328 return nullptr;
7330 if (mInDtor || mOnDestroyCalled || mWindowType == WindowType::Invisible) {
7331 return nullptr;
7334 // In case of popup window return a popup accessible.
7335 nsView* view = nsView::GetViewFor(this);
7336 if (view) {
7337 nsIFrame* frame = view->GetFrame();
7338 if (frame && nsLayoutUtils::IsPopup(frame)) {
7339 nsAccessibilityService* accService = GetOrCreateAccService();
7340 if (accService) {
7341 a11y::DocAccessible* docAcc =
7342 GetAccService()->GetDocAccessible(frame->PresShell());
7343 if (docAcc) {
7344 NS_LOG_WMGETOBJECT(
7345 this, mWnd,
7346 docAcc->GetAccessibleOrDescendant(frame->GetContent()));
7347 return docAcc->GetAccessibleOrDescendant(frame->GetContent());
7353 // otherwise root document accessible.
7354 NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
7355 return GetRootAccessible();
7357 #endif
7359 /**************************************************************
7360 **************************************************************
7362 ** BLOCK: Transparency
7364 ** Window transparency helpers.
7366 **************************************************************
7367 **************************************************************/
7369 void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) {
7370 if (aMode == mTransparencyMode) return;
7372 // stop on dialogs and popups!
7373 HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
7374 nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd);
7376 if (!parent) {
7377 NS_WARNING("Trying to use transparent chrome in an embedded context");
7378 return;
7381 if (parent != this) {
7382 NS_WARNING(
7383 "Setting SetWindowTranslucencyInner on a parent this is not us!");
7386 if (aMode == TransparencyMode::Transparent) {
7387 // If we're switching to the use of a transparent window, hide the chrome
7388 // on our parent.
7389 HideWindowChrome(true);
7390 } else if (mHideChrome &&
7391 mTransparencyMode == TransparencyMode::Transparent) {
7392 // if we're switching out of transparent, re-enable our parent's chrome.
7393 HideWindowChrome(false);
7396 LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE),
7397 exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
7399 if (parent->mIsVisible) {
7400 style |= WS_VISIBLE;
7401 if (parent->mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
7402 style |= WS_MAXIMIZE;
7403 } else if (parent->mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
7404 style |= WS_MINIMIZE;
7408 if (aMode == TransparencyMode::Transparent)
7409 exStyle |= WS_EX_LAYERED;
7410 else
7411 exStyle &= ~WS_EX_LAYERED;
7413 VERIFY_WINDOW_STYLE(style);
7414 ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
7415 ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
7417 mTransparencyMode = aMode;
7419 if (mCompositorWidgetDelegate) {
7420 mCompositorWidgetDelegate->UpdateTransparency(aMode);
7424 /**************************************************************
7425 **************************************************************
7427 ** BLOCK: Popup rollup hooks
7429 ** Deals with CaptureRollup on popup windows.
7431 **************************************************************
7432 **************************************************************/
7434 // Schedules a timer for a window, so we can rollup after processing the hook
7435 // event
7436 void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
7437 // In some cases multiple hooks may be scheduled
7438 // so ignore any other requests once one timer is scheduled
7439 if (sHookTimerId == 0) {
7440 // Remember the window handle and the message ID to be used later
7441 sRollupMsgId = aMsgId;
7442 sRollupMsgWnd = aWnd;
7443 // Schedule native timer for doing the rollup after
7444 // this event is done being processed
7445 sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
7446 NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
7450 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7451 int gLastMsgCode = 0;
7452 extern MSGFEventMsgInfo gMSGFEvents[];
7453 #endif
7455 // Process Menu messages, rollup when popup is clicked.
7456 LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
7457 LPARAM lParam) {
7458 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7459 if (sProcessHook) {
7460 MSG* pMsg = (MSG*)lParam;
7462 int inx = 0;
7463 while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
7464 inx++;
7466 if (code != gLastMsgCode) {
7467 if (gMSGFEvents[inx].mId == code) {
7468 # ifdef DEBUG
7469 MOZ_LOG(gWindowsLog, LogLevel::Info,
7470 ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
7471 gMSGFEvents[inx].mStr, pMsg->hwnd));
7472 # endif
7473 } else {
7474 # ifdef DEBUG
7475 MOZ_LOG(gWindowsLog, LogLevel::Info,
7476 ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
7477 gMSGFEvents[inx].mId, pMsg->hwnd));
7478 # endif
7480 gLastMsgCode = code;
7482 PrintEvent(pMsg->message, FALSE, FALSE);
7484 #endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7486 if (sProcessHook && code == MSGF_MENU) {
7487 MSG* pMsg = (MSG*)lParam;
7488 ScheduleHookTimer(pMsg->hwnd, pMsg->message);
7491 return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
7494 // Process all mouse messages. Roll up when a click is in a native window
7495 // that doesn't have an nsIWidget.
7496 LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
7497 LPARAM lParam) {
7498 if (sProcessHook) {
7499 switch (WinUtils::GetNativeMessage(wParam)) {
7500 case WM_LBUTTONDOWN:
7501 case WM_RBUTTONDOWN:
7502 case WM_MBUTTONDOWN:
7503 case WM_MOUSEWHEEL:
7504 case WM_MOUSEHWHEEL: {
7505 MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
7506 nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
7507 if (!mozWin) {
7508 ScheduleHookTimer(ms->hwnd, (UINT)wParam);
7510 break;
7514 return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
7517 // Process all messages. Roll up when the window is moving, or
7518 // is resizing or when maximized or mininized.
7519 LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
7520 LPARAM lParam) {
7521 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7522 if (sProcessHook) {
7523 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7524 PrintEvent(cwpt->message, FALSE, FALSE);
7526 #endif
7528 if (sProcessHook) {
7529 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7530 if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
7531 cwpt->message == WM_GETMINMAXINFO) {
7532 ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
7536 return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
7539 // Register the special "hooks" for dropdown processing.
7540 void nsWindow::RegisterSpecialDropdownHooks() {
7541 NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
7542 NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
7544 DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
7546 // Install msg hook for moving the window and resizing
7547 if (!sMsgFilterHook) {
7548 DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
7549 sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
7550 nullptr, GetCurrentThreadId());
7551 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7552 if (!sMsgFilterHook) {
7553 MOZ_LOG(gWindowsLog, LogLevel::Info,
7554 ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
7556 #endif
7559 // Install msg hook for menus
7560 if (!sCallProcHook) {
7561 DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
7562 sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
7563 GetCurrentThreadId());
7564 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7565 if (!sCallProcHook) {
7566 MOZ_LOG(
7567 gWindowsLog, LogLevel::Info,
7568 ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
7570 #endif
7573 // Install msg hook for the mouse
7574 if (!sCallMouseHook) {
7575 DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
7576 sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
7577 GetCurrentThreadId());
7578 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7579 if (!sCallMouseHook) {
7580 MOZ_LOG(gWindowsLog, LogLevel::Info,
7581 ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
7583 #endif
7587 // Unhook special message hooks for dropdowns.
7588 void nsWindow::UnregisterSpecialDropdownHooks() {
7589 DISPLAY_NMM_PRT(
7590 "***************** De-installing Msg Hooks ***************\n");
7592 if (sCallProcHook) {
7593 DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
7594 if (!::UnhookWindowsHookEx(sCallProcHook)) {
7595 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
7597 sCallProcHook = nullptr;
7600 if (sMsgFilterHook) {
7601 DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
7602 if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
7603 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
7605 sMsgFilterHook = nullptr;
7608 if (sCallMouseHook) {
7609 DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
7610 if (!::UnhookWindowsHookEx(sCallMouseHook)) {
7611 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
7613 sCallMouseHook = nullptr;
7617 // This timer is designed to only fire one time at most each time a "hook"
7618 // function is used to rollup the dropdown. In some cases, the timer may be
7619 // scheduled from the hook, but that hook event or a subsequent event may roll
7620 // up the dropdown before this timer function is executed.
7622 // For example, if an MFC control takes focus, the combobox will lose focus and
7623 // rollup before this function fires.
7624 VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
7625 DWORD dwTime) {
7626 if (sHookTimerId != 0) {
7627 // if the window is nullptr then we need to use the ID to kill the timer
7628 DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
7629 NS_ASSERTION(status, "Hook Timer was not killed.");
7630 sHookTimerId = 0;
7633 if (sRollupMsgId != 0) {
7634 // Note: DealWithPopups does the check to make sure that the rollup widget
7635 // is set.
7636 LRESULT popupHandlingResult;
7637 nsAutoRollup autoRollup;
7638 DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
7639 sRollupMsgId = 0;
7640 sRollupMsgWnd = nullptr;
7644 static bool IsDifferentThreadWindow(HWND aWnd) {
7645 return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
7648 // static
7649 bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
7650 Maybe<POINT> aEventPoint) {
7651 RECT r;
7652 ::GetWindowRect(aWindow->mWnd, &r);
7653 POINT mp;
7654 if (aEventPoint) {
7655 mp = *aEventPoint;
7656 } else {
7657 DWORD pos = ::GetMessagePos();
7658 mp.x = GET_X_LPARAM(pos);
7659 mp.y = GET_Y_LPARAM(pos);
7662 auto margin = aWindow->mInputRegion.mMargin;
7663 if (margin > 0) {
7664 r.top += margin;
7665 r.bottom -= margin;
7666 r.left += margin;
7667 r.right -= margin;
7670 // was the event inside this window?
7671 return static_cast<bool>(::PtInRect(&r, mp));
7674 // static
7675 bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
7676 uint32_t* aPopupsToRollup,
7677 Maybe<POINT> aEventPoint) {
7678 // If we're dealing with menus, we probably have submenus and we don't want
7679 // to rollup some of them if the click is in a parent menu of the current
7680 // submenu.
7681 *aPopupsToRollup = UINT32_MAX;
7682 AutoTArray<nsIWidget*, 5> widgetChain;
7683 uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
7684 for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
7685 nsIWidget* widget = widgetChain[i];
7686 if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
7687 // Don't roll up if the mouse event occurred within a menu of the
7688 // same type. If the mouse event occurred in a menu higher than that,
7689 // roll up, but pass the number of popups to Rollup so that only those
7690 // of the same type close up.
7691 if (i < sameTypeCount) {
7692 return false;
7695 *aPopupsToRollup = sameTypeCount;
7696 break;
7699 return true;
7702 // static
7703 bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) {
7704 // While popup is open, popup window might be activated by other application.
7705 // At this time, we need to take back focus to the previous window but it
7706 // causes flickering its nonclient area because WM_NCACTIVATE comes before
7707 // WM_ACTIVATE and we cannot know which window will take focus at receiving
7708 // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
7710 // If non-popup window receives WM_NCACTIVATE at deactivating, default
7711 // wndproc shouldn't handle it as deactivating. Instead, at receiving
7712 // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
7713 // This returns true if the window needs to handle WM_NCACTIVATE later.
7715 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7716 return window && !window->IsPopup();
7719 static bool IsTouchSupportEnabled(HWND aWnd) {
7720 nsWindow* topWindow =
7721 WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
7722 return topWindow ? topWindow->IsTouchWindow() : false;
7725 static Maybe<POINT> GetSingleTouch(WPARAM wParam, LPARAM lParam) {
7726 Maybe<POINT> ret;
7727 uint32_t cInputs = LOWORD(wParam);
7728 if (cInputs != 1) {
7729 return ret;
7731 TOUCHINPUT input;
7732 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, &input,
7733 sizeof(TOUCHINPUT))) {
7734 ret.emplace();
7735 ret->x = TOUCH_COORD_TO_PIXEL(input.x);
7736 ret->y = TOUCH_COORD_TO_PIXEL(input.y);
7738 // Note that we don't call CloseTouchInputHandle here because we need
7739 // to read the touch input info again in OnTouch later.
7740 return ret;
7743 // static
7744 bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
7745 LPARAM aLParam, LRESULT* aResult) {
7746 NS_ASSERTION(aResult, "Bad outResult");
7748 // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
7749 *aResult = MA_NOACTIVATE;
7751 if (!::IsWindowVisible(aWnd)) {
7752 return false;
7755 if (MOZ_UNLIKELY(aMessage == WM_KILLFOCUS)) {
7756 // NOTE: We deal with this here rather than on the switch below because we
7757 // want to do this even if there are no menus to rollup (tooltips don't set
7758 // the rollup listener etc).
7759 if (RefPtr pm = nsXULPopupManager::GetInstance()) {
7760 pm->RollupTooltips();
7764 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7765 NS_ENSURE_TRUE(rollupListener, false);
7767 nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
7768 if (!popup) {
7769 return false;
7772 static bool sSendingNCACTIVATE = false;
7773 static bool sPendingNCACTIVATE = false;
7774 uint32_t popupsToRollup = UINT32_MAX;
7776 bool consumeRollupEvent = false;
7777 Maybe<POINT> touchPoint; // In screen coords.
7779 // If we rollup with animations but get occluded right away, we might not
7780 // advance the refresh driver enough for the animation to finish.
7781 auto allowAnimations = nsIRollupListener::AllowAnimations::Yes;
7782 nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
7783 UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
7784 switch (nativeMessage) {
7785 case WM_TOUCH:
7786 if (!IsTouchSupportEnabled(aWnd)) {
7787 // If APZ is disabled, don't allow touch inputs to dismiss popups. The
7788 // compatibility mouse events will do it instead.
7789 return false;
7791 touchPoint = GetSingleTouch(aWParam, aLParam);
7792 if (!touchPoint) {
7793 return false;
7795 [[fallthrough]];
7796 case WM_LBUTTONDOWN:
7797 case WM_RBUTTONDOWN:
7798 case WM_MBUTTONDOWN:
7799 case WM_NCLBUTTONDOWN:
7800 case WM_NCRBUTTONDOWN:
7801 case WM_NCMBUTTONDOWN:
7802 if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
7803 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
7804 // If any of these mouse events are really compatibility events that
7805 // Windows is sending for touch inputs, then don't allow them to dismiss
7806 // popups when APZ is enabled (instead we do the dismissing as part of
7807 // WM_TOUCH handling which is more correct).
7808 // If we don't do this, then when the user lifts their finger after a
7809 // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
7810 // us will dismiss the contextmenu popup that we displayed as part of
7811 // handling the long-tap-up.
7812 return false;
7814 if (!EventIsInsideWindow(popupWindow, touchPoint) &&
7815 GetPopupsToRollup(rollupListener, &popupsToRollup, touchPoint)) {
7816 break;
7818 return false;
7819 case WM_POINTERDOWN: {
7820 WinPointerEvents pointerEvents;
7821 if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) {
7822 return false;
7824 POINT pt;
7825 pt.x = GET_X_LPARAM(aLParam);
7826 pt.y = GET_Y_LPARAM(aLParam);
7827 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
7828 return false;
7830 if (EventIsInsideWindow(popupWindow, Some(pt))) {
7831 // Don't roll up if the event is inside the popup window.
7832 return false;
7834 } break;
7835 case MOZ_WM_DMANIP: {
7836 POINT pt;
7837 ::GetCursorPos(&pt);
7838 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
7839 return false;
7841 if (EventIsInsideWindow(popupWindow, Some(pt))) {
7842 // Don't roll up if the event is inside the popup window
7843 return false;
7845 } break;
7846 case WM_MOUSEWHEEL:
7847 case WM_MOUSEHWHEEL:
7848 // We need to check if the popup thinks that it should cause closing
7849 // itself when mouse wheel events are fired outside the rollup widget.
7850 if (!EventIsInsideWindow(popupWindow)) {
7851 // Check if we should consume this event even if we don't roll-up:
7852 consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
7853 *aResult = MA_ACTIVATE;
7854 if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
7855 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7856 break;
7859 return consumeRollupEvent;
7861 case WM_ACTIVATEAPP:
7862 allowAnimations = nsIRollupListener::AllowAnimations::No;
7863 break;
7865 case WM_ACTIVATE: {
7866 WndProcUrgentInvocation::Marker _marker;
7868 // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
7869 // because we cannot distinguish it's caused by mouse or not.
7870 if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
7871 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7872 if (window && (window->IsPopup() || window->mIsAlert)) {
7873 // Cancel notifying widget listeners of deactivating the previous
7874 // active window (see WM_KILLFOCUS case in ProcessMessage()).
7875 sJustGotDeactivate = false;
7876 // Reactivate the window later.
7877 ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
7878 return true;
7880 // Don't rollup the popup when focus moves back to the parent window
7881 // from a popup because such case is caused by strange mouse drivers.
7882 nsWindow* prevWindow =
7883 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
7884 if (prevWindow && prevWindow->IsPopup()) {
7885 // Consume this message here since previous window must not have
7886 // been inactivated since we've already stopped accepting the
7887 // inactivation below.
7888 return true;
7890 } else if (LOWORD(aWParam) == WA_INACTIVE) {
7891 nsWindow* activeWindow =
7892 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
7893 if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) {
7894 // If focus moves to non-popup widget or focusable popup, the window
7895 // needs to update its nonclient area.
7896 if (!activeWindow || !activeWindow->IsPopup()) {
7897 sSendingNCACTIVATE = true;
7898 ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
7899 sSendingNCACTIVATE = false;
7901 sPendingNCACTIVATE = false;
7903 // If focus moves from/to popup, we don't need to rollup the popup
7904 // because such case is caused by strange mouse drivers. And in
7905 // such case, we should consume the message here since we need to
7906 // hide this odd focus move from our content. (If we didn't consume
7907 // the message here, ProcessMessage() will notify widget listener of
7908 // inactivation and that causes unnecessary reflow for supporting
7909 // -moz-window-inactive pseudo class.
7910 if (activeWindow) {
7911 if (activeWindow->IsPopup()) {
7912 return true;
7914 nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
7915 if (deactiveWindow && deactiveWindow->IsPopup()) {
7916 return true;
7919 } else if (LOWORD(aWParam) == WA_CLICKACTIVE) {
7920 // If the WM_ACTIVATE message is caused by a click in a popup,
7921 // we should not rollup any popups.
7922 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7923 if ((window && window->IsPopup()) ||
7924 !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7925 return false;
7928 allowAnimations = nsIRollupListener::AllowAnimations::No;
7929 } break;
7931 case MOZ_WM_REACTIVATE:
7932 // The previous active window should take back focus.
7933 if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
7934 // FYI: Even without this API call, you see expected result (e.g., the
7935 // owner window of the popup keeps active without flickering
7936 // the non-client area). And also this causes initializing
7937 // TSF and it causes using CPU time a lot. However, even if we
7938 // consume WM_ACTIVE messages, native focus change has already
7939 // been occurred. I.e., a popup window is active now. Therefore,
7940 // you'll see some odd behavior if we don't reactivate the owner
7941 // window here. For example, if you do:
7942 // 1. Turn wheel on a bookmark panel.
7943 // 2. Turn wheel on another window.
7944 // then, you'll see that the another window becomes active but the
7945 // owner window of the bookmark panel looks still active and the
7946 // bookmark panel keeps open. The reason is that the first wheel
7947 // operation gives focus to the bookmark panel. Therefore, when
7948 // the next operation gives focus to the another window, previous
7949 // focus window is the bookmark panel (i.e., a popup window).
7950 // So, in this case, our hack around here prevents to inactivate
7951 // the owner window and roll up the bookmark panel.
7952 ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
7954 return true;
7956 case WM_NCACTIVATE:
7957 if (!aWParam && !sSendingNCACTIVATE &&
7958 NeedsToHandleNCActivateDelayed(aWnd)) {
7959 // Don't just consume WM_NCACTIVATE. It doesn't handle only the
7960 // nonclient area state change.
7961 ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
7962 // Accept the deactivating because it's necessary to receive following
7963 // WM_ACTIVATE.
7964 *aResult = TRUE;
7965 sPendingNCACTIVATE = true;
7966 return true;
7968 return false;
7970 case WM_MOUSEACTIVATE:
7971 if (!EventIsInsideWindow(popupWindow) &&
7972 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7973 // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
7974 // of TweakUI is enabled. Then, check if the popup should be rolled up
7975 // with rollup listener. If not, just consume the message.
7976 if (HIWORD(aLParam) == WM_MOUSEMOVE &&
7977 !rollupListener->ShouldRollupOnMouseActivate()) {
7978 return true;
7980 // Otherwise, it should be handled by wndproc.
7981 return false;
7984 // Prevent the click inside the popup from causing a change in window
7985 // activation. Since the popup is shown non-activated, we need to eat any
7986 // requests to activate the window while it is displayed. Windows will
7987 // automatically activate the popup on the mousedown otherwise.
7988 return true;
7990 case WM_SHOWWINDOW:
7991 // If the window is being minimized, close popups.
7992 if (aLParam == SW_PARENTCLOSING) {
7993 allowAnimations = nsIRollupListener::AllowAnimations::No;
7994 break;
7996 return false;
7998 case WM_KILLFOCUS:
7999 // If focus moves to other window created in different process/thread,
8000 // e.g., a plugin window, popups should be rolled up.
8001 if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
8002 allowAnimations = nsIRollupListener::AllowAnimations::No;
8003 break;
8005 return false;
8007 case WM_MOVING:
8008 case WM_MENUSELECT:
8009 break;
8011 default:
8012 return false;
8015 // Only need to deal with the last rollup for left mouse down events.
8016 NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
8018 nsIRollupListener::RollupOptions rollupOptions{
8019 popupsToRollup,
8020 nsIRollupListener::FlushViews::Yes,
8021 /* mPoint = */ nullptr,
8022 allowAnimations,
8025 if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
8026 nativeMessage == WM_POINTERDOWN) {
8027 LayoutDeviceIntPoint pos;
8028 if (nativeMessage == WM_TOUCH) {
8029 pos.x = touchPoint->x;
8030 pos.y = touchPoint->y;
8031 } else {
8032 POINT pt;
8033 pt.x = GET_X_LPARAM(aLParam);
8034 pt.y = GET_Y_LPARAM(aLParam);
8035 // POINTERDOWN is already in screen coords.
8036 if (nativeMessage == WM_LBUTTONDOWN) {
8037 ::ClientToScreen(aWnd, &pt);
8039 pos = LayoutDeviceIntPoint(pt.x, pt.y);
8042 rollupOptions.mPoint = &pos;
8043 nsIContent* lastRollup = nullptr;
8044 consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
8045 nsAutoRollup::SetLastRollup(lastRollup);
8046 } else {
8047 consumeRollupEvent = rollupListener->Rollup(rollupOptions);
8050 // Tell hook to stop processing messages
8051 sProcessHook = false;
8052 sRollupMsgId = 0;
8053 sRollupMsgWnd = nullptr;
8055 // If we are NOT supposed to be consuming events, let it go through
8056 if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) {
8057 *aResult = MA_ACTIVATE;
8058 return true;
8061 return false;
8064 /**************************************************************
8065 **************************************************************
8067 ** BLOCK: Misc. utility methods and functions.
8069 ** General use.
8071 **************************************************************
8072 **************************************************************/
8074 // Note that the result of GetTopLevelWindow method can be different from the
8075 // result of WinUtils::GetTopLevelHWND(). The result can be non-floating
8076 // window. Because our top level window may be contained in another window
8077 // which is not managed by us.
8078 nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
8079 nsWindow* curWindow = this;
8081 while (true) {
8082 if (aStopOnDialogOrPopup) {
8083 switch (curWindow->mWindowType) {
8084 case WindowType::Dialog:
8085 case WindowType::Popup:
8086 return curWindow;
8087 default:
8088 break;
8092 // Retrieve the top level parent or owner window
8093 nsWindow* parentWindow = curWindow->GetParentWindow(true);
8095 if (!parentWindow) return curWindow;
8097 curWindow = parentWindow;
8101 // Set a flag if hwnd is a (non-popup) visible window from this process,
8102 // and bail out of the enumeration. Otherwise leave the flag unmodified
8103 // and continue the enumeration.
8104 // lParam must be a bool* pointing at the flag to be set.
8105 static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
8106 DWORD pid;
8107 ::GetWindowThreadProcessId(hwnd, &pid);
8108 if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
8109 // Don't count popups as visible windows, since they don't take focus,
8110 // in case we only have a popup visible (see bug 1554490 where the gfx
8111 // test window is an offscreen popup).
8112 nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
8113 if (!window || !window->IsPopup()) {
8114 bool* windowsVisible = reinterpret_cast<bool*>(lParam);
8115 *windowsVisible = true;
8116 return FALSE;
8119 return TRUE;
8122 // Determine if it would be ok to activate a window, taking focus.
8123 // We want to avoid stealing focus from another app (bug 225305).
8124 bool nsWindow::CanTakeFocus() {
8125 HWND fgWnd = ::GetForegroundWindow();
8126 if (!fgWnd) {
8127 // There is no foreground window, so don't worry about stealing focus.
8128 return true;
8130 // We can take focus if the current foreground window is already from
8131 // this process.
8132 DWORD pid;
8133 ::GetWindowThreadProcessId(fgWnd, &pid);
8134 if (pid == ::GetCurrentProcessId()) {
8135 return true;
8138 bool windowsVisible = false;
8139 ::EnumWindows(EnumVisibleWindowsProc,
8140 reinterpret_cast<LPARAM>(&windowsVisible));
8142 if (!windowsVisible) {
8143 // We're probably creating our first visible window, allow that to
8144 // take focus.
8145 return true;
8147 return false;
8150 /* static */ const wchar_t* nsWindow::GetMainWindowClass() {
8151 static const wchar_t* sMainWindowClass = nullptr;
8152 if (!sMainWindowClass) {
8153 nsAutoString className;
8154 Preferences::GetString("ui.window_class_override", className);
8155 if (!className.IsEmpty()) {
8156 sMainWindowClass = wcsdup(className.get());
8157 } else {
8158 sMainWindowClass = kClassNameGeneral;
8161 return sMainWindowClass;
8164 LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
8165 POINT pt;
8166 pt.x = GET_X_LPARAM(lParam);
8167 pt.y = GET_Y_LPARAM(lParam);
8168 ::ClientToScreen(mWnd, &pt);
8169 return MAKELPARAM(pt.x, pt.y);
8172 LPARAM nsWindow::lParamToClient(LPARAM lParam) {
8173 POINT pt;
8174 pt.x = GET_X_LPARAM(lParam);
8175 pt.y = GET_Y_LPARAM(lParam);
8176 ::ScreenToClient(mWnd, &pt);
8177 return MAKELPARAM(pt.x, pt.y);
8180 WPARAM nsWindow::wParamFromGlobalMouseState() {
8181 WPARAM result = 0;
8183 if (!!::GetKeyState(VK_CONTROL)) {
8184 result |= MK_CONTROL;
8187 if (!!::GetKeyState(VK_SHIFT)) {
8188 result |= MK_SHIFT;
8191 if (!!::GetKeyState(VK_LBUTTON)) {
8192 result |= MK_LBUTTON;
8195 if (!!::GetKeyState(VK_MBUTTON)) {
8196 result |= MK_MBUTTON;
8199 if (!!::GetKeyState(VK_RBUTTON)) {
8200 result |= MK_RBUTTON;
8203 if (!!::GetKeyState(VK_XBUTTON1)) {
8204 result |= MK_XBUTTON1;
8207 if (!!::GetKeyState(VK_XBUTTON2)) {
8208 result |= MK_XBUTTON2;
8211 return result;
8214 // WORKAROUND FOR UNDOCUMENTED BEHAVIOR: `IFileDialog::Show` disables the
8215 // top-level ancestor of its provided owner-window. If the modal window's
8216 // container process crashes, it will never get a chance to undo that.
8218 // For simplicity's sake we simply unconditionally perform both the disabling
8219 // and reenabling here, synchronously, on the main thread, rather than leaving
8220 // it to happen in our asynchronously-operated IFileDialog.
8222 void nsWindow::PickerOpen() {
8223 AssertIsOnMainThread();
8225 // Disable the root-level window synchronously before any file-dialogs get a
8226 // chance to fight over doing it asynchronously.
8227 if (!mPickerDisplayCount) {
8228 ::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), FALSE);
8231 mPickerDisplayCount++;
8234 void nsWindow::PickerClosed() {
8235 AssertIsOnMainThread();
8236 NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
8237 if (!mPickerDisplayCount) return;
8238 mPickerDisplayCount--;
8240 // Once all the file-dialogs are gone, reenable the root-level window.
8241 if (!mPickerDisplayCount) {
8242 ::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), TRUE);
8243 DispatchFocusToTopLevelWindow(true);
8246 if (!mPickerDisplayCount && mDestroyCalled) {
8247 Destroy();
8251 bool nsWindow::WidgetTypeSupportsAcceleration() {
8252 // We don't currently support using an accelerated layer manager with
8253 // transparent windows so don't even try. I'm also not sure if we even
8254 // want to support this case. See bug 593471.
8256 // Windows' support for transparent accelerated surfaces isn't great.
8257 // Some possible approaches:
8258 // - Readback the data and update it using
8259 // UpdateLayeredWindow/UpdateLayeredWindowIndirect
8260 // This is what WPF does. See
8261 // CD3DDeviceLevel1::PresentWithGDI/CD3DSwapChainWithSwDC in WpfGfx. The
8262 // rationale for not using IDirect3DSurface9::GetDC is explained here:
8263 // https://web.archive.org/web/20160521191104/https://blogs.msdn.microsoft.com/dwayneneed/2008/09/08/transparent-windows-in-wpf/
8264 // - Use D3D11_RESOURCE_MISC_GDI_COMPATIBLE, IDXGISurface1::GetDC(),
8265 // and UpdateLayeredWindowIndirect.
8266 // This is suggested here:
8267 // https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/december/windows-with-c-layered-windows-with-direct2d
8268 // but might have the same problem that IDirect3DSurface9::GetDC has.
8269 // - Creating the window with the WS_EX_NOREDIRECTIONBITMAP flag and use
8270 // DirectComposition.
8271 // Not supported on Win7.
8272 // - Using DwmExtendFrameIntoClientArea with negative margins and something
8273 // to turn off the glass effect.
8274 // This doesn't work when the DWM is not running (Win7)
8276 // Also see bug 1150376, D3D11 composition can cause issues on some devices
8277 // on Windows 7 where presentation fails randomly for windows with drop
8278 // shadows.
8279 return mTransparencyMode != TransparencyMode::Transparent &&
8280 !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
8283 bool nsWindow::DispatchTouchEventFromWMPointer(
8284 UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo,
8285 mozilla::MouseButton aButton) {
8286 MultiTouchInput::MultiTouchType touchType;
8287 switch (msg) {
8288 case WM_POINTERDOWN:
8289 touchType = MultiTouchInput::MULTITOUCH_START;
8290 break;
8291 case WM_POINTERUPDATE:
8292 if (aPointerInfo.mPressure == 0) {
8293 return false; // hover
8295 touchType = MultiTouchInput::MULTITOUCH_MOVE;
8296 break;
8297 case WM_POINTERUP:
8298 touchType = MultiTouchInput::MULTITOUCH_END;
8299 break;
8300 default:
8301 return false;
8304 nsPointWin touchPoint;
8305 touchPoint.x = GET_X_LPARAM(aLParam);
8306 touchPoint.y = GET_Y_LPARAM(aLParam);
8307 touchPoint.ScreenToClient(mWnd);
8309 SingleTouchData touchData(static_cast<int32_t>(aPointerInfo.pointerId),
8310 ScreenIntPoint::FromUnknownPoint(touchPoint),
8311 ScreenSize(1, 1), // pixel size radius for pen
8312 0.0f, // no radius rotation
8313 aPointerInfo.mPressure);
8314 touchData.mTiltX = aPointerInfo.tiltX;
8315 touchData.mTiltY = aPointerInfo.tiltY;
8316 touchData.mTwist = aPointerInfo.twist;
8318 MultiTouchInput touchInput;
8319 touchInput.mType = touchType;
8320 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
8321 touchInput.mTouches.AppendElement(touchData);
8322 touchInput.mButton = aButton;
8323 touchInput.mButtons = aPointerInfo.mButtons;
8325 // POINTER_INFO.dwKeyStates can't be used as it only supports Shift and Ctrl
8326 ModifierKeyState modifierKeyState;
8327 touchInput.modifiers = modifierKeyState.GetModifiers();
8329 DispatchTouchInput(touchInput, MouseEvent_Binding::MOZ_SOURCE_PEN);
8330 return true;
8333 static MouseButton PenFlagsToMouseButton(PEN_FLAGS aPenFlags) {
8334 // Theoretically flags can be set together but they do not
8335 if (aPenFlags & PEN_FLAG_BARREL) {
8336 return MouseButton::eSecondary;
8338 if (aPenFlags & PEN_FLAG_ERASER) {
8339 return MouseButton::eEraser;
8341 return MouseButton::ePrimary;
8344 bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
8345 if (!mAPZC) {
8346 // APZ is not available on context menu. Follow the behavior of touch input
8347 // which fallbacks to WM_LBUTTON* and WM_GESTURE, to keep consistency.
8348 return false;
8350 if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) {
8351 return false;
8353 if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
8354 // We have to handle WM_POINTER* to fetch and cache pen related information
8355 // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
8356 // handler. This is because Windows doesn't support ::DoDragDrop in the
8357 // touch or pen message handlers.
8358 mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam);
8359 // Don't consume the Windows WM_POINTER* messages
8360 return false;
8363 uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
8364 POINTER_PEN_INFO penInfo{};
8365 if (!mPointerEvents.GetPointerPenInfo(pointerId, &penInfo)) {
8366 return false;
8369 // When dispatching mouse events with pen, there may be some
8370 // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
8371 // small movements. Those events will reset sLastMousePoint and reset
8372 // sLastClickCount. To prevent that, we keep the last pen down position
8373 // and compare it with the subsequent WM_POINTERUPDATE. If the movement is
8374 // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
8375 // eMouseMove for WM_POINTERUPDATE.
8376 static POINT sLastPointerDownPoint = {0};
8378 // We don't support chorded buttons for pen. Keep the button at
8379 // WM_POINTERDOWN.
8380 static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
8381 static bool sPointerDown = false;
8383 EventMessage message;
8384 mozilla::MouseButton button = MouseButton::ePrimary;
8385 switch (msg) {
8386 case WM_POINTERDOWN: {
8387 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8388 GET_Y_LPARAM(aLParam));
8389 sLastPointerDownPoint.x = eventPoint.x;
8390 sLastPointerDownPoint.y = eventPoint.y;
8391 message = eMouseDown;
8392 button = PenFlagsToMouseButton(penInfo.penFlags);
8393 sLastPenDownButton = button;
8394 sPointerDown = true;
8395 } break;
8396 case WM_POINTERUP:
8397 message = eMouseUp;
8398 MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
8399 button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
8400 sPointerDown = false;
8401 break;
8402 case WM_POINTERUPDATE:
8403 message = eMouseMove;
8404 if (sPointerDown) {
8405 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8406 GET_Y_LPARAM(aLParam));
8407 int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
8408 ? sLastPointerDownPoint.x - eventPoint.x.value
8409 : eventPoint.x.value - sLastPointerDownPoint.x;
8410 int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
8411 ? sLastPointerDownPoint.y - eventPoint.y.value
8412 : eventPoint.y.value - sLastPointerDownPoint.y;
8413 bool insideMovementThreshold =
8414 movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
8415 movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
8417 if (insideMovementThreshold) {
8418 // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
8419 // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
8420 return false;
8422 button = sLastPenDownButton;
8424 break;
8425 case WM_POINTERLEAVE:
8426 message = eMouseExitFromWidget;
8427 break;
8428 default:
8429 return false;
8432 // Windows defines the pen pressure is normalized to a range between 0 and
8433 // 1024. Convert it to float.
8434 float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
8435 int16_t buttons = sPointerDown
8436 ? nsContentUtils::GetButtonsFlagForButton(button)
8437 : MouseButtonsFlag::eNoButtons;
8438 WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure,
8439 buttons);
8440 // Per
8441 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_pen_info,
8442 // the rotation is normalized in a range of 0 to 359.
8443 MOZ_ASSERT(penInfo.rotation <= 359);
8444 pointerInfo.twist = (int32_t)penInfo.rotation;
8446 // Fire touch events but not when the barrel button is pressed.
8447 if (button != MouseButton::eSecondary &&
8448 StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled() &&
8449 DispatchTouchEventFromWMPointer(msg, aLParam, pointerInfo, button)) {
8450 return true;
8453 // The aLParam of WM_POINTER* is the screen location. Convert it to client
8454 // location
8455 LPARAM newLParam = lParamToClient(aLParam);
8456 DispatchMouseEvent(message, aWParam, newLParam, false, button,
8457 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8459 if (button == MouseButton::eSecondary && message == eMouseUp) {
8460 // Fire eContextMenu manually since consuming WM_POINTER* blocks
8461 // WM_CONTEXTMENU
8462 DispatchMouseEvent(eContextMenu, aWParam, newLParam, false, button,
8463 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8465 // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
8466 // WM_MOUSEMOVE.
8467 return true;
8470 void nsWindow::GetCompositorWidgetInitData(
8471 mozilla::widget::CompositorWidgetInitData* aInitData) {
8472 *aInitData = WinCompositorWidgetInitData(
8473 reinterpret_cast<uintptr_t>(mWnd),
8474 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
8475 mTransparencyMode, mFrameState->GetSizeMode());
8478 bool nsWindow::SynchronouslyRepaintOnResize() { return false; }
8480 void nsWindow::MaybeDispatchInitialFocusEvent() {
8481 if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
8482 DispatchFocusToTopLevelWindow(true);
8486 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
8487 nsCOMPtr<nsIWidget> window = new nsWindow();
8488 return window.forget();
8491 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
8492 nsCOMPtr<nsIWidget> window = new nsWindow(true);
8493 return window.forget();
8496 // static
8497 bool nsWindow::InitTouchInjection() {
8498 if (!sTouchInjectInitialized) {
8499 // Initialize touch injection on the first call
8500 HMODULE hMod = LoadLibraryW(kUser32LibName);
8501 if (!hMod) {
8502 return false;
8505 InitializeTouchInjectionPtr func =
8506 (InitializeTouchInjectionPtr)GetProcAddress(hMod,
8507 "InitializeTouchInjection");
8508 if (!func) {
8509 WinUtils::Log("InitializeTouchInjection not available.");
8510 return false;
8513 if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
8514 WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
8515 GetLastError());
8516 return false;
8519 sInjectTouchFuncPtr =
8520 (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
8521 if (!sInjectTouchFuncPtr) {
8522 WinUtils::Log("InjectTouchInput not available.");
8523 return false;
8525 sTouchInjectInitialized = true;
8527 return true;
8530 bool nsWindow::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
8531 POINTER_FLAGS aFlags, uint32_t aPressure,
8532 uint32_t aOrientation) {
8533 if (aId > TOUCH_INJECT_MAX_POINTS) {
8534 WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
8535 return false;
8538 POINTER_TOUCH_INFO info{};
8540 info.touchFlags = TOUCH_FLAG_NONE;
8541 info.touchMask =
8542 TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
8543 info.pressure = aPressure;
8544 info.orientation = aOrientation;
8546 info.pointerInfo.pointerFlags = aFlags;
8547 info.pointerInfo.pointerType = PT_TOUCH;
8548 info.pointerInfo.pointerId = aId;
8549 info.pointerInfo.ptPixelLocation.x = aPoint.x;
8550 info.pointerInfo.ptPixelLocation.y = aPoint.y;
8552 info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
8553 info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
8554 info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
8555 info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
8557 for (int i = 0; i < 3; i++) {
8558 if (sInjectTouchFuncPtr(1, &info)) {
8559 break;
8561 DWORD error = GetLastError();
8562 if (error == ERROR_NOT_READY && i < 2) {
8563 // We sent it too quickly after the previous injection (see bug 1535140
8564 // comment 10). On the first loop iteration we just yield (via Sleep(0))
8565 // and try again. If it happens again on the second loop iteration we
8566 // explicitly Sleep(1) and try again. If that doesn't work either we just
8567 // error out.
8568 ::Sleep(i);
8569 continue;
8571 WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
8572 return false;
8574 return true;
8577 void nsWindow::ChangedDPI() {
8578 if (mWidgetListener) {
8579 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
8580 presShell->BackingScaleFactorChanged();
8583 NotifyAPZOfDPIChange();
8586 static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
8587 nsWindow::TouchPointerState aPointerState, bool isUpdate) {
8588 bool hover = aPointerState & nsWindow::TOUCH_HOVER;
8589 bool contact = aPointerState & nsWindow::TOUCH_CONTACT;
8590 bool remove = aPointerState & nsWindow::TOUCH_REMOVE;
8591 bool cancel = aPointerState & nsWindow::TOUCH_CANCEL;
8593 POINTER_FLAGS flags;
8594 if (isUpdate) {
8595 // We know about this pointer, send an update
8596 flags = POINTER_FLAG_UPDATE;
8597 if (hover) {
8598 flags |= POINTER_FLAG_INRANGE;
8599 } else if (contact) {
8600 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
8601 } else if (remove) {
8602 flags = POINTER_FLAG_UP;
8605 if (cancel) {
8606 flags |= POINTER_FLAG_CANCELED;
8608 } else {
8609 // Missing init state, error out
8610 if (remove || cancel) {
8611 return Err(NS_ERROR_INVALID_ARG);
8614 // Create a new pointer
8615 flags = POINTER_FLAG_INRANGE;
8616 if (contact) {
8617 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
8620 return flags;
8623 nsresult nsWindow::SynthesizeNativeTouchPoint(
8624 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8625 LayoutDeviceIntPoint aPoint, double aPointerPressure,
8626 uint32_t aPointerOrientation, nsIObserver* aObserver) {
8627 AutoObserverNotifier notifier(aObserver, "touchpoint");
8629 if (StaticPrefs::apz_test_fails_with_native_injection() ||
8630 !InitTouchInjection()) {
8631 // If we don't have touch injection from the OS, or if we are running a test
8632 // that cannot properly inject events to satisfy the OS requirements (see
8633 // bug 1313170) we can just fake it and synthesize the events from here.
8634 MOZ_ASSERT(NS_IsMainThread());
8635 if (aPointerState == TOUCH_HOVER) {
8636 return NS_ERROR_UNEXPECTED;
8639 if (!mSynthesizedTouchInput) {
8640 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
8643 WidgetEventTime time = CurrentMessageWidgetEventTime();
8644 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8645 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
8646 mSynthesizedTouchInput.get(), time.mTimeStamp, aPointerId,
8647 aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
8648 DispatchTouchInput(inputToDispatch);
8649 return NS_OK;
8652 // win api expects a value from 0 to 1024. aPointerPressure is a value
8653 // from 0.0 to 1.0.
8654 uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
8656 // If we already know about this pointer id get it's record
8657 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8658 POINTER_FLAGS flags;
8659 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8660 auto result = PointerStateToFlag(aPointerState, !!entry);
8661 if (result.isOk()) {
8662 flags = result.unwrap();
8663 } else {
8664 return result.unwrapErr();
8667 if (!entry) {
8668 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8669 PointerInfo::PointerType::TOUCH));
8670 } else {
8671 if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
8672 return NS_ERROR_UNEXPECTED;
8674 if (aPointerState & TOUCH_REMOVE) {
8675 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8676 // so shouldn't leak.
8677 entry.Remove();
8681 return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
8682 aPointerOrientation)
8683 ? NS_ERROR_UNEXPECTED
8684 : NS_OK;
8688 nsresult nsWindow::ClearNativeTouchSequence(nsIObserver* aObserver) {
8689 AutoObserverNotifier notifier(aObserver, "cleartouch");
8690 if (!sTouchInjectInitialized) {
8691 return NS_OK;
8694 // cancel all input points
8695 for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
8696 auto* info = iter.UserData();
8697 if (info->mType != PointerInfo::PointerType::TOUCH) {
8698 continue;
8700 InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
8701 iter.Remove();
8704 nsBaseWidget::ClearNativeTouchSequence(nullptr);
8706 return NS_OK;
8709 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8710 static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
8711 static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
8712 static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
8713 #endif
8714 static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
8716 static bool InitPenInjection() {
8717 if (sSyntheticPenDevice) {
8718 return true;
8720 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8721 HMODULE hMod = LoadLibraryW(kUser32LibName);
8722 if (!hMod) {
8723 return false;
8725 CreateSyntheticPointerDevice =
8726 (CreateSyntheticPointerDevicePtr)GetProcAddress(
8727 hMod, "CreateSyntheticPointerDevice");
8728 if (!CreateSyntheticPointerDevice) {
8729 WinUtils::Log("CreateSyntheticPointerDevice not available.");
8730 return false;
8732 DestroySyntheticPointerDevice =
8733 (DestroySyntheticPointerDevicePtr)GetProcAddress(
8734 hMod, "DestroySyntheticPointerDevice");
8735 if (!DestroySyntheticPointerDevice) {
8736 WinUtils::Log("DestroySyntheticPointerDevice not available.");
8737 return false;
8739 InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
8740 hMod, "InjectSyntheticPointerInput");
8741 if (!InjectSyntheticPointerInput) {
8742 WinUtils::Log("InjectSyntheticPointerInput not available.");
8743 return false;
8745 #endif
8746 sSyntheticPenDevice =
8747 CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
8748 return !!sSyntheticPenDevice;
8751 nsresult nsWindow::SynthesizeNativePenInput(
8752 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8753 LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
8754 int32_t aTiltX, int32_t aTiltY, int32_t aButton, nsIObserver* aObserver) {
8755 AutoObserverNotifier notifier(aObserver, "peninput");
8756 if (!InitPenInjection()) {
8757 return NS_ERROR_UNEXPECTED;
8760 // win api expects a value from 0 to 1024. aPointerPressure is a value
8761 // from 0.0 to 1.0.
8762 uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
8764 // If we already know about this pointer id get it's record
8765 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8766 POINTER_FLAGS flags;
8767 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8768 auto result = PointerStateToFlag(aPointerState, !!entry);
8769 if (result.isOk()) {
8770 flags = result.unwrap();
8771 } else {
8772 return result.unwrapErr();
8775 if (!entry) {
8776 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8777 PointerInfo::PointerType::PEN));
8778 } else {
8779 if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
8780 return NS_ERROR_UNEXPECTED;
8782 if (aPointerState & TOUCH_REMOVE) {
8783 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8784 // so shouldn't leak.
8785 entry.Remove();
8789 POINTER_TYPE_INFO info{};
8791 info.type = PT_PEN;
8792 info.penInfo.pointerInfo.pointerType = PT_PEN;
8793 info.penInfo.pointerInfo.pointerFlags = flags;
8794 info.penInfo.pointerInfo.pointerId = aPointerId;
8795 info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
8796 info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
8798 info.penInfo.penFlags = PEN_FLAG_NONE;
8799 // PEN_FLAG_ERASER is not supported this way, unfortunately.
8800 if (aButton == 2) {
8801 info.penInfo.penFlags |= PEN_FLAG_BARREL;
8803 info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
8804 PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
8805 info.penInfo.pressure = pressure;
8806 info.penInfo.rotation = aRotation;
8807 info.penInfo.tiltX = aTiltX;
8808 info.penInfo.tiltY = aTiltY;
8810 return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
8811 ? NS_OK
8812 : NS_ERROR_UNEXPECTED;
8816 bool nsWindow::HandleAppCommandMsg(const MSG& aAppCommandMsg,
8817 LRESULT* aRetValue) {
8818 ModifierKeyState modKeyState;
8819 NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
8820 bool consumed = nativeKey.HandleAppCommandMessage();
8821 *aRetValue = consumed ? 1 : 0;
8822 return consumed;
8825 #ifdef DEBUG
8826 nsresult nsWindow::SetHiDPIMode(bool aHiDPI) {
8827 return WinUtils::SetHiDPIMode(aHiDPI);
8830 nsresult nsWindow::RestoreHiDPIMode() { return WinUtils::RestoreHiDPIMode(); }
8831 #endif
8833 mozilla::Maybe<UINT> nsWindow::GetHiddenTaskbarEdge() {
8834 HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
8836 // Check all four sides of our monitor for an appbar. Skip any that aren't
8837 // the system taskbar.
8838 MONITORINFO mi;
8839 mi.cbSize = sizeof(MONITORINFO);
8840 ::GetMonitorInfo(windowMonitor, &mi);
8842 APPBARDATA appBarData;
8843 appBarData.cbSize = sizeof(appBarData);
8844 appBarData.rc = mi.rcMonitor;
8845 const auto kEdges = {ABE_BOTTOM, ABE_TOP, ABE_LEFT, ABE_RIGHT};
8846 for (auto edge : kEdges) {
8847 appBarData.uEdge = edge;
8848 HWND appBarHwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &appBarData);
8849 if (appBarHwnd) {
8850 nsAutoString className;
8851 if (WinUtils::GetClassName(appBarHwnd, className)) {
8852 if (className.Equals(L"Shell_TrayWnd") ||
8853 className.Equals(L"Shell_SecondaryTrayWnd")) {
8854 return Some(edge);
8860 return Nothing();
8863 static nsSizeMode GetSizeModeForWindowFrame(HWND aWnd, bool aFullscreenMode) {
8864 WINDOWPLACEMENT pl;
8865 pl.length = sizeof(pl);
8866 ::GetWindowPlacement(aWnd, &pl);
8868 if (pl.showCmd == SW_SHOWMINIMIZED) {
8869 return nsSizeMode_Minimized;
8870 } else if (aFullscreenMode) {
8871 return nsSizeMode_Fullscreen;
8872 } else if (pl.showCmd == SW_SHOWMAXIMIZED) {
8873 return nsSizeMode_Maximized;
8874 } else {
8875 return nsSizeMode_Normal;
8879 static void ShowWindowWithMode(HWND aWnd, nsSizeMode aMode) {
8880 // This will likely cause a callback to
8881 // nsWindow::FrameState::{OnFrameChanging() and OnFrameChanged()}
8882 switch (aMode) {
8883 case nsSizeMode_Fullscreen:
8884 ::ShowWindow(aWnd, SW_SHOW);
8885 break;
8887 case nsSizeMode_Maximized:
8888 ::ShowWindow(aWnd, SW_MAXIMIZE);
8889 break;
8891 case nsSizeMode_Minimized:
8892 ::ShowWindow(aWnd, SW_MINIMIZE);
8893 break;
8895 default:
8896 // Don't call ::ShowWindow if we're trying to "restore" a window that is
8897 // already in a normal state. Prevents a bug where snapping to one side
8898 // of the screen and then minimizing would cause Windows to forget our
8899 // window's correct restored position/size.
8900 if (GetCurrentShowCmd(aWnd) != SW_SHOWNORMAL) {
8901 ::ShowWindow(aWnd, SW_RESTORE);
8906 nsWindow::FrameState::FrameState(nsWindow* aWindow) : mWindow(aWindow) {}
8908 nsSizeMode nsWindow::FrameState::GetSizeMode() const { return mSizeMode; }
8910 void nsWindow::FrameState::CheckInvariant() const {
8911 MOZ_ASSERT(mSizeMode >= 0 && mSizeMode < nsSizeMode_Invalid);
8912 MOZ_ASSERT(mLastSizeMode >= 0 && mLastSizeMode < nsSizeMode_Invalid);
8913 MOZ_ASSERT(mPreFullscreenSizeMode >= 0 &&
8914 mPreFullscreenSizeMode < nsSizeMode_Invalid);
8915 MOZ_ASSERT(mWindow);
8917 // We should never observe fullscreen sizemode unless fullscreen is enabled
8918 MOZ_ASSERT_IF(mSizeMode == nsSizeMode_Fullscreen, mFullscreenMode);
8919 MOZ_ASSERT_IF(!mFullscreenMode, mSizeMode != nsSizeMode_Fullscreen);
8921 // Something went wrong if we somehow saved fullscreen mode when we are
8922 // changing into fullscreen mode
8923 MOZ_ASSERT(mPreFullscreenSizeMode != nsSizeMode_Fullscreen);
8926 void nsWindow::FrameState::ConsumePreXULSkeletonState(bool aWasMaximized) {
8927 mSizeMode = aWasMaximized ? nsSizeMode_Maximized : nsSizeMode_Normal;
8930 void nsWindow::FrameState::EnsureSizeMode(nsSizeMode aMode,
8931 DoShowWindow aDoShowWindow) {
8932 if (mSizeMode == aMode) {
8933 return;
8936 if (StaticPrefs::widget_windows_fullscreen_remind_taskbar()) {
8937 // If we're unminimizing a window, asynchronously notify the taskbar after
8938 // the message has been processed. This redundant notification works around
8939 // a race condition in explorer.exe. (See bug 1835851, or comments in
8940 // TaskbarConcealer.)
8942 // Note that we notify regardless of `aMode`: unminimizing a non-fullscreen
8943 // window can also affect the correct taskbar state, yet fail to affect the
8944 // current taskbar state.
8945 if (mSizeMode == nsSizeMode_Minimized) {
8946 ::PostMessage(mWindow->mWnd, MOZ_WM_FULLSCREEN_STATE_UPDATE, 0, 0);
8950 if (aMode == nsSizeMode_Fullscreen) {
8951 EnsureFullscreenMode(true, aDoShowWindow);
8952 MOZ_ASSERT(mSizeMode == nsSizeMode_Fullscreen);
8953 } else if (mSizeMode == nsSizeMode_Fullscreen && aMode == nsSizeMode_Normal) {
8954 // If we are in fullscreen mode, minimize should work like normal and
8955 // return us to fullscreen mode when unminimized. Maximize isn't really
8956 // available and won't do anything. "Restore" should do the same thing as
8957 // requesting to end fullscreen.
8958 EnsureFullscreenMode(false, aDoShowWindow);
8959 } else {
8960 SetSizeModeInternal(aMode, aDoShowWindow);
8964 void nsWindow::FrameState::EnsureFullscreenMode(bool aFullScreen,
8965 DoShowWindow aDoShowWindow) {
8966 const bool changed = aFullScreen != mFullscreenMode;
8967 if (changed && aFullScreen) {
8968 // Save the size mode from before fullscreen.
8969 mPreFullscreenSizeMode = mSizeMode;
8971 mFullscreenMode = aFullScreen;
8972 if (changed || aFullScreen) {
8973 // NOTE(emilio): When minimizing a fullscreen window we remain with
8974 // mFullscreenMode = true, but mSizeMode = nsSizeMode_Minimized. We need to
8975 // make sure to call SetSizeModeInternal even if mFullscreenMode didn't
8976 // change, to ensure we actually end up with a fullscreen sizemode when
8977 // restoring a window from that state.
8978 SetSizeModeInternal(
8979 aFullScreen ? nsSizeMode_Fullscreen : mPreFullscreenSizeMode,
8980 aDoShowWindow);
8984 void nsWindow::FrameState::OnFrameChanging() {
8985 const nsSizeMode newSizeMode =
8986 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
8987 EnsureSizeMode(newSizeMode);
8988 mWindow->UpdateNonClientMargins(false);
8991 void nsWindow::FrameState::OnFrameChanged() {
8992 // We don't want to perform the ShowWindow ourselves if we're on the frame
8993 // changed message. Windows has done the frame change for us, and we take care
8994 // of activating as needed. We also don't want to potentially trigger
8995 // more focus / restore. Among other things, this addresses a bug on Win7
8996 // related to window docking. (bug 489258)
8997 const auto newSizeMode =
8998 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
8999 EnsureSizeMode(newSizeMode, DoShowWindow::No);
9001 // If window was restored, activate the window now to get correct attributes.
9002 if (mWindow->mIsVisible && mWindow->IsForegroundWindow() &&
9003 mLastSizeMode == nsSizeMode_Minimized &&
9004 mSizeMode != nsSizeMode_Minimized) {
9005 mWindow->DispatchFocusToTopLevelWindow(true);
9007 mLastSizeMode = mSizeMode;
9010 static void MaybeLogSizeMode(nsSizeMode aMode) {
9011 #ifdef WINSTATE_DEBUG_OUTPUT
9012 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** SizeMode: %d\n", int(aMode)));
9013 #endif
9016 void nsWindow::FrameState::SetSizeModeInternal(nsSizeMode aMode,
9017 DoShowWindow aDoShowWindow) {
9018 if (mSizeMode == aMode) {
9019 return;
9022 const auto oldSizeMode = mSizeMode;
9023 const bool fullscreenChange =
9024 mSizeMode == nsSizeMode_Fullscreen || aMode == nsSizeMode_Fullscreen;
9025 const bool fullscreen = aMode == nsSizeMode_Fullscreen;
9027 mLastSizeMode = mSizeMode;
9028 mSizeMode = aMode;
9030 MaybeLogSizeMode(mSizeMode);
9032 if (bool(aDoShowWindow) && mWindow->mIsVisible) {
9033 ShowWindowWithMode(mWindow->mWnd, aMode);
9036 mWindow->UpdateNonClientMargins(false);
9038 if (fullscreenChange) {
9039 mWindow->OnFullscreenChanged(oldSizeMode, fullscreen);
9042 mWindow->OnSizeModeChange();
9045 void nsWindow::ContextMenuPreventer::Update(
9046 const WidgetMouseEvent& aEvent,
9047 const nsIWidget::ContentAndAPZEventStatus& aEventStatus) {
9048 mNeedsToPreventContextMenu =
9049 aEvent.mMessage == eMouseUp &&
9050 aEvent.mButton == MouseButton::eSecondary &&
9051 aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
9052 aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault;