Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / widget / windows / nsWindow.cpp
blob118766bfea95a31eb150139d61b50be3d570e949
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 "nsWindow.h"
82 #include "nsWindowTaskbarConcealer.h"
83 #include "nsAppRunner.h"
85 #include <shellapi.h>
86 #include <windows.h>
87 #include <wtsapi32.h>
88 #include <process.h>
89 #include <commctrl.h>
90 #include <dbt.h>
91 #include <unknwn.h>
92 #include <psapi.h>
93 #include <rpc.h>
94 #include <propvarutil.h>
95 #include <propkey.h>
97 #include "mozilla/Logging.h"
98 #include "prtime.h"
99 #include "prenv.h"
101 #include "mozilla/WidgetTraceEvent.h"
102 #include "nsContentUtils.h"
103 #include "nsISupportsPrimitives.h"
104 #include "nsITheme.h"
105 #include "nsIObserverService.h"
106 #include "nsIScreenManager.h"
107 #include "imgIContainer.h"
108 #include "nsIFile.h"
109 #include "nsIRollupListener.h"
110 #include "nsIClipboard.h"
111 #include "WinMouseScrollHandler.h"
112 #include "nsFontMetrics.h"
113 #include "nsIFontEnumerator.h"
114 #include "nsFont.h"
115 #include "nsRect.h"
116 #include "nsThreadUtils.h"
117 #include "nsNativeCharsetUtils.h"
118 #include "nsGkAtoms.h"
119 #include "nsCRT.h"
120 #include "nsAppDirectoryServiceDefs.h"
121 #include "nsWidgetsCID.h"
122 #include "nsTHashtable.h"
123 #include "nsHashKeys.h"
124 #include "nsString.h"
125 #include "mozilla/Components.h"
126 #include "nsNativeThemeWin.h"
127 #include "nsXULPopupManager.h"
128 #include "nsWindowsDllInterceptor.h"
129 #include "nsLayoutUtils.h"
130 #include "nsView.h"
131 #include "nsWindowGfx.h"
132 #include "gfxWindowsPlatform.h"
133 #include "gfxDWriteFonts.h"
134 #include "nsPrintfCString.h"
135 #include "mozilla/Preferences.h"
136 #include "SystemTimeConverter.h"
137 #include "WinTaskbar.h"
138 #include "WidgetUtils.h"
139 #include "WinWindowOcclusionTracker.h"
140 #include "nsIWidgetListener.h"
141 #include "mozilla/dom/Document.h"
142 #include "mozilla/dom/MouseEventBinding.h"
143 #include "mozilla/dom/Touch.h"
144 #include "mozilla/gfx/2D.h"
145 #include "mozilla/gfx/GPUProcessManager.h"
146 #include "mozilla/intl/LocaleService.h"
147 #include "mozilla/layers/WebRenderLayerManager.h"
148 #include "mozilla/WindowsVersion.h"
149 #include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
150 #include "mozilla/TextEventDispatcherListener.h"
151 #include "mozilla/widget/nsAutoRollup.h"
152 #include "mozilla/widget/PlatformWidgetTypes.h"
153 #include "mozilla/widget/Screen.h"
154 #include "nsStyleConsts.h"
155 #include "nsBidiKeyboard.h"
156 #include "nsStyleConsts.h"
157 #include "gfxConfig.h"
158 #include "InProcessWinCompositorWidget.h"
159 #include "InputDeviceUtils.h"
160 #include "ScreenHelperWin.h"
161 #include "mozilla/StaticPrefs_apz.h"
162 #include "mozilla/StaticPrefs_dom.h"
163 #include "mozilla/StaticPrefs_gfx.h"
164 #include "mozilla/StaticPrefs_layout.h"
165 #include "mozilla/StaticPrefs_widget.h"
166 #include "nsNativeAppSupportWin.h"
167 #include "mozilla/browser/NimbusFeatures.h"
169 #include "nsIGfxInfo.h"
170 #include "nsUXThemeConstants.h"
171 #include "KeyboardLayout.h"
172 #include "nsNativeDragTarget.h"
173 #include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
174 #include <zmouse.h>
175 #include <richedit.h>
177 #if defined(ACCESSIBILITY)
179 # ifdef DEBUG
180 # include "mozilla/a11y/Logging.h"
181 # endif
183 # include "oleidl.h"
184 # include <winuser.h>
185 # include "nsAccessibilityService.h"
186 # include "mozilla/a11y/DocAccessible.h"
187 # include "mozilla/a11y/LazyInstantiator.h"
188 # include "mozilla/a11y/Platform.h"
189 # if !defined(WINABLEAPI)
190 # include <winable.h>
191 # endif // !defined(WINABLEAPI)
192 #endif // defined(ACCESSIBILITY)
194 #include "WindowsUIUtils.h"
196 #include "nsWindowDefs.h"
198 #include "nsCrashOnException.h"
200 #include "nsIContent.h"
202 #include "mozilla/BackgroundHangMonitor.h"
203 #include "WinIMEHandler.h"
205 #include "npapi.h"
207 #include <d3d11.h>
209 // ERROR from wingdi.h (below) gets undefined by some code.
210 // #define ERROR 0
211 // #define RGN_ERROR ERROR
212 #define ERROR 0
214 #if !defined(SM_CONVERTIBLESLATEMODE)
215 # define SM_CONVERTIBLESLATEMODE 0x2003
216 #endif
218 #include "mozilla/gfx/DeviceManagerDx.h"
219 #include "mozilla/layers/APZInputBridge.h"
220 #include "mozilla/layers/InputAPZContext.h"
221 #include "mozilla/layers/KnowsCompositor.h"
222 #include "InputData.h"
224 #include "mozilla/TaskController.h"
225 #include "mozilla/Telemetry.h"
226 #include "mozilla/webrender/WebRenderAPI.h"
227 #include "mozilla/layers/IAPZCTreeManager.h"
229 #include "DirectManipulationOwner.h"
231 using namespace mozilla;
232 using namespace mozilla::dom;
233 using namespace mozilla::gfx;
234 using namespace mozilla::layers;
235 using namespace mozilla::widget;
236 using namespace mozilla::plugins;
238 /**************************************************************
239 **************************************************************
241 ** BLOCK: Variables
243 ** nsWindow Class static initializations and global variables.
245 **************************************************************
246 **************************************************************/
248 /**************************************************************
250 * SECTION: nsWindow statics
252 **************************************************************/
253 static const wchar_t kUser32LibName[] = L"user32.dll";
255 uint32_t nsWindow::sInstanceCount = 0;
256 bool nsWindow::sIsOleInitialized = false;
257 nsIWidget::Cursor nsWindow::sCurrentCursor = {};
258 nsWindow* nsWindow::sCurrentWindow = nullptr;
259 bool nsWindow::sJustGotDeactivate = false;
260 bool nsWindow::sJustGotActivate = false;
261 bool nsWindow::sIsInMouseCapture = false;
263 // Urgent-message reentrancy depth for the static `WindowProc` callback.
265 // Three unfortunate facts collide:
267 // 𝛼) Some messages must be processed promptly. If not, Windows will leave the
268 // receiving window in an intermediate, and potentially unusable, state until
269 // the WindowProc invocation that is handling it returns.
271 // 𝛽) Some messages have indefinitely long processing time. These are mostly
272 // messages which may cause us to enter a nested modal loop (via
273 // `SpinEventLoopUntil` or similar).
275 // 𝛾) Sometimes, messages skip the queue entirely. Our `WindowProc` may be
276 // reentrantly reinvoked from the kernel while we're blocking _on_ the
277 // kernel, even briefly, during processing of other messages. (Relevant
278 // search term: `KeUserModeCallback`.)
280 // The nightmare scenario, then, is that during processing of an 𝛼-message, we
281 // briefly become blocked (e.g., by calling `::SendMessageW()`), and the kernel
282 // takes that opportunity to use 𝛾 to hand us a 𝛽-message. (Concretely, see
283 // bug 1842170.)
285 // There is little we can do to prevent the first half of this scenario. 𝛼) and
286 // 𝛾) are effectively immutable facts of Windows, and we sometimes legitimately
287 // need to make blocking calls to process 𝛼-messages. (We may not even be aware
288 // that we're making such calls, if they're undocumented implementation details
289 // of another API.)
291 // In an ideal world, WindowProc would always return promptly (or at least in
292 // bounded time), and 𝛽-messages would not _per se_ exist; long-running modal
293 // states would instead be implemented in async fashion. In practice, that's far
294 // easier said than done -- replacing existing uses of `SpinEventLoopUntil` _et
295 // al._ with asynchronous mechanisms is a collection of mostly-unrelated cross-
296 // cutting architectural tasks, each of potentially unbounded scope. For now,
297 // and for the foreseeable future, we're stuck with them.
299 // We therefore simply punt. More specifically: if a known 𝛽-message jumps the
300 // queue to come in while we're in the middle of processing a known 𝛼-message,
301 // we:
302 // * properly queue the message for processing later;
303 // * respond to the 𝛽-message as though we actually had processed it; and
304 // * just hope that it can wait until we get around to it.
306 // The word "known" requires a bit of justification. There is no canonical set
307 // of 𝛼-messages, nor is the set of 𝛽-messages fixed (or even demarcable). We
308 // can't safely assume that all messages are 𝛼-messages, as that could cause
309 // 𝛽-messages to be arbitrarily and surprisingly delayed whenever any nested
310 // event loop is active. We also can't assume all messages are 𝛽-messages,
311 // since one 𝛼-message jumping the queue while processing another 𝛼-message is
312 // part of normal and required operation for windowed Windows applications.
314 // So we simply add messages to those sets as we identify them. (Or, preferably,
315 // rework the 𝛽-message's handling to make it no longer 𝛽. But see above.)
317 // ---
319 // The actual value of `sDepth` is the number of active invocations of
320 // `WindowProc` that are processing known 𝛼-messages.
321 size_t nsWindow::WndProcUrgentInvocation::sDepth = 0;
323 // Hook Data Members for Dropdowns. sProcessHook Tells the
324 // hook methods whether they should be processing the hook
325 // messages.
326 HHOOK nsWindow::sMsgFilterHook = nullptr;
327 HHOOK nsWindow::sCallProcHook = nullptr;
328 HHOOK nsWindow::sCallMouseHook = nullptr;
329 bool nsWindow::sProcessHook = false;
330 UINT nsWindow::sRollupMsgId = 0;
331 HWND nsWindow::sRollupMsgWnd = nullptr;
332 UINT nsWindow::sHookTimerId = 0;
334 // Used to prevent dispatching mouse events that do not originate from user
335 // input.
336 POINT nsWindow::sLastMouseMovePoint = {0};
338 bool nsWindow::sIsRestoringSession = false;
340 bool nsWindow::sTouchInjectInitialized = false;
341 InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
343 static SystemTimeConverter<DWORD>& TimeConverter() {
344 static SystemTimeConverter<DWORD> timeConverterSingleton;
345 return timeConverterSingleton;
348 // Global event hook for window cloaking. Never deregistered.
349 // - `Nothing` if not yet set.
350 // - `Some(nullptr)` if no attempt should be made to set it.
351 static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook = Nothing();
352 static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
354 namespace mozilla {
356 class CurrentWindowsTimeGetter {
357 public:
358 explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
360 DWORD GetCurrentTime() const { return ::GetTickCount(); }
362 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
363 DWORD currentTime = GetCurrentTime();
364 if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
365 // There's already one inflight with this timestamp. Don't
366 // send a duplicate.
367 return;
369 sBackwardsSkewStamp = Some(aNow);
370 sLastPostTime = currentTime;
371 static_assert(sizeof(WPARAM) >= sizeof(DWORD),
372 "Can't fit a DWORD in a WPARAM");
373 ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
376 static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
377 TimeStamp* aOutSkewStamp) {
378 if (aPostTime != sLastPostTime) {
379 // The SKEWFIX message is stale; we've sent a new one since then.
380 // Ignore this one.
381 return false;
383 MOZ_ASSERT(sBackwardsSkewStamp);
384 *aOutSkewStamp = sBackwardsSkewStamp.value();
385 sBackwardsSkewStamp = Nothing();
386 return true;
389 private:
390 static Maybe<TimeStamp> sBackwardsSkewStamp;
391 static DWORD sLastPostTime;
392 HWND mWnd;
395 Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
396 DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
398 } // namespace mozilla
400 /**************************************************************
402 * SECTION: globals variables
404 **************************************************************/
406 static const char* sScreenManagerContractID =
407 "@mozilla.org/gfx/screenmanager;1";
409 extern mozilla::LazyLogModule gWindowsLog;
411 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
413 // General purpose user32.dll hook object
414 static WindowsDllInterceptor sUser32Intercept;
416 // When the client area is extended out into the default window frame area,
417 // this is the minimum amount of space along the edge of resizable windows
418 // we will always display a resize cursor in, regardless of the underlying
419 // content.
420 static const int32_t kResizableBorderMinSize = 3;
422 // Getting this object from the window server can be expensive. Keep it
423 // around, also get it off the main thread. (See bug 1640852)
424 StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
425 static bool gInitializedVirtualDesktopManager = false;
427 // We should never really try to accelerate windows bigger than this. In some
428 // cases this might lead to no D3D9 acceleration where we could have had it
429 // but D3D9 does not reliably report when it supports bigger windows. 8192
430 // is as safe as we can get, we know at least D3D10 hardware always supports
431 // this, other hardware we expect to report correctly in D3D9.
432 #define MAX_ACCELERATED_DIMENSION 8192
434 // On window open (as well as after), Windows has an unfortunate habit of
435 // sending rather a lot of WM_NCHITTEST messages. Because we have to do point
436 // to DOM target conversions for these, we cache responses for a given
437 // coordinate this many milliseconds:
438 #define HITTEST_CACHE_LIFETIME_MS 50
440 #if defined(ACCESSIBILITY)
442 namespace mozilla {
445 * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
446 * injecting tiptsf.dll. The touchscreen process then posts registered messages
447 * to our main thread. The tiptsf hook picks up those registered messages and
448 * uses them as commands, some of which call into UIA, which then calls into
449 * MSAA, which then sends WM_GETOBJECT to us.
451 * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
452 * hook. Since thread-local hooks are called ahead of global hooks, we will
453 * see these registered messages before tiptsf does. At this point we can then
454 * raise a flag that blocks a11y before invoking CallNextHookEx which will then
455 * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
456 * flag by calling TIPMessageHandler::IsA11yBlocked().
458 * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
459 * function that also calls into UIA.
461 class TIPMessageHandler {
462 public:
463 ~TIPMessageHandler() {
464 if (mHook) {
465 ::UnhookWindowsHookEx(mHook);
469 static void Initialize() {
470 if (sInstance) {
471 return;
474 sInstance = new TIPMessageHandler();
475 ClearOnShutdown(&sInstance);
478 static bool IsA11yBlocked() {
479 if (!sInstance) {
480 return false;
483 return sInstance->mA11yBlockCount > 0;
486 private:
487 TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
488 MOZ_ASSERT(NS_IsMainThread());
490 // Registered messages used by tiptsf
491 mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
492 mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
493 mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
494 mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
495 mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
496 mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
497 mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
499 mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
500 ::GetCurrentThreadId());
501 MOZ_ASSERT(mHook);
503 if (!sSendMessageTimeoutWStub) {
504 sUser32Intercept.Init("user32.dll");
505 DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
506 sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
507 MOZ_ASSERT(hooked);
511 class MOZ_RAII A11yInstantiationBlocker {
512 public:
513 A11yInstantiationBlocker() {
514 if (!TIPMessageHandler::sInstance) {
515 return;
517 ++TIPMessageHandler::sInstance->mA11yBlockCount;
520 ~A11yInstantiationBlocker() {
521 if (!TIPMessageHandler::sInstance) {
522 return;
524 MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
525 --TIPMessageHandler::sInstance->mA11yBlockCount;
529 friend class A11yInstantiationBlocker;
531 static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
532 if (aCode < 0 || !sInstance) {
533 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
536 MSG* msg = reinterpret_cast<MSG*>(aLParam);
537 UINT& msgCode = msg->message;
539 for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
540 if (msgCode == sInstance->mMessages[i]) {
541 A11yInstantiationBlocker block;
542 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
546 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
549 static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
550 WPARAM aWParam, LPARAM aLParam,
551 UINT aFlags, UINT aTimeout,
552 PDWORD_PTR aMsgResult) {
553 // We don't want to handle this unless the message is a WM_GETOBJECT that we
554 // want to block, and the aHwnd is a nsWindow that belongs to the current
555 // (i.e., main) thread.
556 if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
557 static_cast<LONG>(aLParam) != OBJID_CLIENT || !::NS_IsMainThread() ||
558 !WinUtils::GetNSWindowPtr(aHwnd) || !IsA11yBlocked()) {
559 return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
560 aTimeout, aMsgResult);
563 // In this case we want to fake the result that would happen if we had
564 // decided not to handle WM_GETOBJECT in our WndProc. We hand the message
565 // off to DefWindowProc to accomplish this.
566 *aMsgResult = static_cast<DWORD_PTR>(
567 ::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
569 return static_cast<LRESULT>(TRUE);
572 static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
573 sSendMessageTimeoutWStub;
574 static StaticAutoPtr<TIPMessageHandler> sInstance;
576 HHOOK mHook;
577 UINT mMessages[7];
578 uint32_t mA11yBlockCount;
581 WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
582 TIPMessageHandler::sSendMessageTimeoutWStub;
583 StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
585 } // namespace mozilla
587 #endif // defined(ACCESSIBILITY)
589 namespace mozilla {
591 // This task will get the VirtualDesktopManager from the generic thread pool
592 // since doing this on the main thread on startup causes performance issues.
594 // See bug 1640852.
596 // This should be fine and should not require any locking, as when the main
597 // thread will access it, if it races with this function it will either find
598 // it to be null or to have a valid value.
599 class InitializeVirtualDesktopManagerTask : public Task {
600 public:
601 InitializeVirtualDesktopManagerTask()
602 : Task(Kind::OffMainThreadOnly, kDefaultPriorityValue) {}
604 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
605 bool GetName(nsACString& aName) override {
606 aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
607 return true;
609 #endif
611 virtual TaskResult Run() override {
612 RefPtr<IVirtualDesktopManager> desktopManager;
613 HRESULT hr = ::CoCreateInstance(
614 CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
615 __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
616 if (FAILED(hr)) {
617 return TaskResult::Complete;
620 gVirtualDesktopManager = desktopManager;
621 return TaskResult::Complete;
625 // Ground-truth query: does Windows claim the window is cloaked right now?
626 static bool IsCloaked(HWND hwnd) {
627 DWORD cloakedState;
628 HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
629 sizeof(cloakedState));
631 if (FAILED(hr)) {
632 MOZ_LOG(sCloakingLog, LogLevel::Warning,
633 ("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
634 return false;
637 return cloakedState != 0;
640 } // namespace mozilla
642 /**************************************************************
643 **************************************************************
645 ** BLOCK: nsIWidget impl.
647 ** nsIWidget interface implementation, broken down into
648 ** sections.
650 **************************************************************
651 **************************************************************/
653 /**************************************************************
655 * SECTION: nsWindow construction and destruction
657 **************************************************************/
659 nsWindow::nsWindow(bool aIsChildWindow)
660 : nsBaseWidget(BorderStyle::Default),
661 mBrush(::CreateSolidBrush(NSRGB_2_COLOREF(::GetSysColor(COLOR_BTNFACE)))),
662 mFrameState(std::in_place, this),
663 mIsChildWindow(aIsChildWindow),
664 mLastPaintEndTime(TimeStamp::Now()),
665 mCachedHitTestTime(TimeStamp::Now()),
666 mSizeConstraintsScale(GetDefaultScale().scale),
667 mDesktopId("DesktopIdMutex") {
668 MOZ_ASSERT(mWindowType == WindowType::Child);
670 if (!gInitializedVirtualDesktopManager) {
671 TaskController::Get()->AddTask(
672 MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
673 gInitializedVirtualDesktopManager = true;
676 // Global initialization
677 if (!sInstanceCount) {
678 // Global app registration id for Win7 and up. See
679 // WinTaskbar.cpp for details.
680 // MSIX packages explicitly do not support setting the appid from within
681 // the app, as it is set in the package manifest instead.
682 if (!WinUtils::HasPackageIdentity()) {
683 mozilla::widget::WinTaskbar::RegisterAppUserModelID();
685 KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
686 #if defined(ACCESSIBILITY)
687 mozilla::TIPMessageHandler::Initialize();
688 #endif // defined(ACCESSIBILITY)
689 if (SUCCEEDED(::OleInitialize(nullptr))) {
690 sIsOleInitialized = true;
692 NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
693 MouseScrollHandler::Initialize();
694 // Init theme data
695 nsUXThemeData::UpdateNativeThemeInfo();
696 RedirectedKeyDownMessageManager::Forget();
697 } // !sInstanceCount
699 sInstanceCount++;
702 nsWindow::~nsWindow() {
703 mInDtor = true;
705 // If the widget was released without calling Destroy() then the native window
706 // still exists, and we need to destroy it. Destroy() will early-return if it
707 // was already called. In any case it is important to call it before
708 // destroying mPresentLock (cf. 1156182).
709 Destroy();
711 // Free app icon resources. This must happen after `OnDestroy` (see bug
712 // 708033).
713 if (mIconSmall) ::DestroyIcon(mIconSmall);
715 if (mIconBig) ::DestroyIcon(mIconBig);
717 sInstanceCount--;
719 // Global shutdown
720 if (sInstanceCount == 0) {
721 IMEHandler::Terminate();
722 sCurrentCursor = {};
723 if (sIsOleInitialized) {
724 ::OleFlushClipboard();
725 ::OleUninitialize();
726 sIsOleInitialized = false;
730 NS_IF_RELEASE(mNativeDragTarget);
733 /**************************************************************
735 * SECTION: nsIWidget::Create, nsIWidget::Destroy
737 * Creating and destroying windows for this widget.
739 **************************************************************/
741 // Allow Derived classes to modify the height that is passed
742 // when the window is created or resized.
743 int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
745 void nsWindow::SendAnAPZEvent(InputData& aEvent) {
746 LRESULT popupHandlingResult;
747 if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
748 // We need to consume the event after using it to roll up the popup(s).
749 return;
752 if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
753 // Give the swipe tracker a first pass at the event. If a new pan gesture
754 // has been started since the beginning of the swipe, the swipe tracker
755 // will know to ignore the event.
756 nsEventStatus status =
757 mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
758 if (status == nsEventStatus_eConsumeNoDefault) {
759 return;
763 APZEventResult result;
764 if (mAPZC) {
765 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
767 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
768 return;
771 MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
772 aEvent.mInputType == PINCHGESTURE_INPUT);
774 if (aEvent.mInputType == PANGESTURE_INPUT) {
775 PanGestureInput& panInput = aEvent.AsPanGestureInput();
776 WidgetWheelEvent event = panInput.ToWidgetEvent(this);
777 if (!mAPZC) {
778 if (MayStartSwipeForNonAPZ(panInput)) {
779 return;
781 } else {
782 event = MayStartSwipeForAPZ(panInput, result);
785 ProcessUntransformedAPZEvent(&event, result);
787 return;
790 PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
791 WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
792 ProcessUntransformedAPZEvent(&event, result);
795 void nsWindow::RecreateDirectManipulationIfNeeded() {
796 DestroyDirectManipulation();
798 if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
799 return;
802 if (!(StaticPrefs::apz_allow_zooming() ||
803 StaticPrefs::apz_windows_use_direct_manipulation()) ||
804 StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
805 return;
808 mDmOwner = MakeUnique<DirectManipulationOwner>(this);
810 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
811 GetHeight(mBounds.Height()));
812 mDmOwner->Init(bounds);
815 void nsWindow::ResizeDirectManipulationViewport() {
816 if (mDmOwner) {
817 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
818 GetHeight(mBounds.Height()));
819 mDmOwner->ResizeViewport(bounds);
823 void nsWindow::DestroyDirectManipulation() {
824 if (mDmOwner) {
825 mDmOwner->Destroy();
826 mDmOwner.reset();
830 // Create the proper widget
831 nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
832 const LayoutDeviceIntRect& aRect,
833 widget::InitData* aInitData) {
834 // Historical note: there was once some belief and/or intent that nsWindows
835 // could be created on arbitrary threads, and this may still be reflected in
836 // some comments.
837 MOZ_ASSERT(NS_IsMainThread());
839 widget::InitData defaultInitData;
840 if (!aInitData) aInitData = &defaultInitData;
842 nsIWidget* baseParent =
843 aInitData->mWindowType == WindowType::Dialog ||
844 aInitData->mWindowType == WindowType::TopLevel ||
845 aInitData->mWindowType == WindowType::Invisible
846 ? nullptr
847 : aParent;
849 mIsTopWidgetWindow = (nullptr == baseParent);
850 mBounds = aRect;
852 // Ensure that the toolkit is created.
853 nsToolkit::GetToolkit();
855 BaseCreate(baseParent, aInitData);
857 HWND parent;
858 if (aParent) { // has a nsIWidget parent
859 parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
860 mParent = aParent;
861 } else { // has a nsNative parent
862 parent = (HWND)aNativeParent;
863 mParent =
864 aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
867 mIsRTL = aInitData->mRTL;
868 mForMenupopupFrame = aInitData->mForMenupopupFrame;
869 mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
870 mAlwaysOnTop = aInitData->mAlwaysOnTop;
871 mResizable = aInitData->mResizable;
873 DWORD style = WindowStyle();
874 DWORD extendedStyle = WindowExStyle();
876 if (mWindowType == WindowType::Popup) {
877 if (!aParent) {
878 parent = nullptr;
880 } else if (mWindowType == WindowType::Invisible) {
881 // Make sure CreateWindowEx succeeds at creating a toplevel window
882 style &= ~0x40000000; // WS_CHILDWINDOW
883 } else {
884 // See if the caller wants to explictly set clip children and clip siblings
885 if (aInitData->mClipChildren) {
886 style |= WS_CLIPCHILDREN;
887 } else {
888 style &= ~WS_CLIPCHILDREN;
890 if (aInitData->mClipSiblings) {
891 style |= WS_CLIPSIBLINGS;
895 const wchar_t* className = ChooseWindowClass(mWindowType, mForMenupopupFrame);
897 // Take specific actions when creating the first top-level window
898 static bool sFirstTopLevelWindowCreated = false;
899 if (aInitData->mWindowType == WindowType::TopLevel && !aParent &&
900 !sFirstTopLevelWindowCreated) {
901 sFirstTopLevelWindowCreated = true;
902 mWnd = ConsumePreXULSkeletonUIHandle();
903 auto skeletonUIError = GetPreXULSkeletonUIErrorReason();
904 if (skeletonUIError) {
905 nsAutoString errorString(
906 GetPreXULSkeletonUIErrorString(skeletonUIError.value()));
907 Telemetry::ScalarSet(
908 Telemetry::ScalarID::STARTUP_SKELETON_UI_DISABLED_REASON,
909 errorString);
911 if (mWnd) {
912 MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
913 "The skeleton UI window style should match the expected "
914 "style for the first window created");
915 MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
916 "The skeleton UI window extended style should match the "
917 "expected extended style for the first window created");
918 MOZ_ASSERT(
919 ::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
920 "The skeleton UI window should be created on the same thread as "
921 "other windows");
922 mIsShowingPreXULSkeletonUI = true;
924 // If we successfully consumed the pre-XUL skeleton UI, just update
925 // our internal state to match what is currently being displayed.
926 mIsVisible = true;
927 mIsCloaked = mozilla::IsCloaked(mWnd);
928 mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
930 // These match the margins set in browser-tabsintitlebar.js with
931 // default prefs on Windows. Bug 1673092 tracks lining this up with
932 // that more correctly instead of hard-coding it.
933 SetNonClientMargins(LayoutDeviceIntMargin(0, 2, 2, 2));
935 // Reset the WNDPROC for this window and its whole class, as we had
936 // to use our own WNDPROC when creating the the skeleton UI window.
937 ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
938 reinterpret_cast<LONG_PTR>(
939 WinUtils::NonClientDpiScalingDefWindowProcW));
940 ::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
941 reinterpret_cast<LONG_PTR>(
942 WinUtils::NonClientDpiScalingDefWindowProcW));
946 if (!mWnd) {
947 mWnd =
948 ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
949 aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
950 parent, nullptr, nsToolkit::mDllInstance, nullptr);
953 if (!mWnd) {
954 NS_WARNING("nsWindow CreateWindowEx failed.");
955 return NS_ERROR_FAILURE;
958 if (!sWinCloakEventHook) {
959 MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
961 // C++03 lambda approximation until P2173R1 is available (-std=c++2b)
962 struct StdcallLambda {
963 static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
964 DWORD event, HWND hwnd,
965 LONG idObject, LONG idChild,
966 DWORD idEventThread,
967 DWORD dwmsEventTime) {
968 const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
969 nsWindow::OnCloakEvent(hwnd, isCloaked);
973 const HWINEVENTHOOK hook = ::SetWinEventHook(
974 EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
975 &StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
976 ::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
977 sWinCloakEventHook = Some(hook);
979 if (!hook) {
980 const DWORD err = ::GetLastError();
981 MOZ_LOG(sCloakingLog, LogLevel::Error,
982 ("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
983 err));
987 if (aInitData->mIsPrivate) {
988 if (NimbusFeatures::GetBool("majorRelease2022"_ns,
989 "feltPrivacyWindowSeparation"_ns, true) &&
990 // Although permanent Private Browsing mode is indeed Private Browsing,
991 // we choose to make it look like regular Firefox in terms of the icon
992 // it uses (which also means we shouldn't use the Private Browsing
993 // AUMID).
994 !StaticPrefs::browser_privatebrowsing_autostart()) {
995 RefPtr<IPropertyStore> pPropStore;
996 if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
997 getter_AddRefs(pPropStore)))) {
998 PROPVARIANT pv;
999 nsAutoString aumid;
1000 // make sure we're using the private browsing AUMID so that taskbar
1001 // grouping works properly
1002 Unused << NS_WARN_IF(
1003 !mozilla::widget::WinTaskbar::GenerateAppUserModelID(aumid, true));
1004 if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
1005 if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
1006 pPropStore->Commit();
1009 PropVariantClear(&pv);
1012 HICON icon = ::LoadIconW(::GetModuleHandleW(nullptr),
1013 MAKEINTRESOURCEW(IDI_PBMODE));
1014 SetBigIcon(icon);
1015 SetSmallIcon(icon);
1019 mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
1021 // If mDefaultScale is set before mWnd has been set, it will have the scale of
1022 // the primary monitor, rather than the monitor that the window is actually
1023 // on. For non-popup windows this gets corrected by the WM_DPICHANGED message
1024 // which resets mDefaultScale, but for popup windows we don't reset
1025 // mDefaultScale on that message. In order to ensure that popup windows
1026 // spawned on a non-primary monitor end up with the correct scale, we reset
1027 // mDefaultScale here so that it gets recomputed using the correct monitor now
1028 // that we have a mWnd.
1029 mDefaultScale = -1.0;
1031 if (mIsRTL) {
1032 DWORD dwAttribute = TRUE;
1033 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1034 sizeof dwAttribute);
1037 UpdateDarkModeToolbar();
1039 if (mOpeningAnimationSuppressed) {
1040 SuppressAnimation(true);
1043 if (mAlwaysOnTop) {
1044 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
1045 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1048 if (mWindowType != WindowType::Invisible &&
1049 MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
1050 // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
1052 // We create two zero-sized windows as descendants of the top-level window,
1053 // like so:
1055 // Top-level window (MozillaWindowClass)
1056 // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
1057 // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
1059 // We need to have the middle window, otherwise the Trackpoint driver
1060 // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
1061 // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
1062 // window hierarchy until they are handled by nsWindow::WindowProc.
1063 // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
1064 // but these do not propagate automatically, so we have the window
1065 // procedure pretend that they were dispatched to the top-level window
1066 // instead.
1068 // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
1069 // is given below so that it catches the Trackpoint driver's heuristics.
1070 HWND scrollContainerWnd = ::CreateWindowW(
1071 className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
1072 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1073 HWND scrollableWnd = ::CreateWindowW(
1074 className, L"FAKETRACKPOINTSCROLLABLE",
1075 WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
1076 scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1078 // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
1079 // WindowProcInternal can distinguish it from the top-level window
1080 // easily.
1081 ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
1083 // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
1084 // old window procedure in its "user data".
1085 WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
1086 scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
1087 ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
1090 // We will start receiving native events after associating with our native
1091 // window. We will also become the output of WinUtils::GetNSWindowPtr for that
1092 // window.
1093 if (!AssociateWithNativeWindow()) {
1094 return NS_ERROR_FAILURE;
1097 // Starting with Windows XP, a process always runs within a terminal services
1098 // session. In order to play nicely with RDP, fast user switching, and the
1099 // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
1100 // our HWND in order to receive this message.
1101 DebugOnly<BOOL> wtsRegistered =
1102 ::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
1103 NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
1105 mDefaultIMC.Init(this);
1106 IMEHandler::InitInputContext(this, mInputContext);
1108 static bool a11yPrimed = false;
1109 if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
1110 a11yPrimed = true;
1111 if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
1112 ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
1116 RecreateDirectManipulationIfNeeded();
1118 return NS_OK;
1121 void nsWindow::LocalesChanged() {
1122 bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
1123 if (mIsRTL != isRTL) {
1124 DWORD dwAttribute = isRTL;
1125 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1126 sizeof dwAttribute);
1127 mIsRTL = isRTL;
1131 // Close this nsWindow
1132 void nsWindow::Destroy() {
1133 // WM_DESTROY has already fired, avoid calling it twice
1134 if (mOnDestroyCalled) return;
1136 // Don't destroy windows that have file pickers open, we'll tear these down
1137 // later once the picker is closed.
1138 mDestroyCalled = true;
1139 if (mPickerDisplayCount) return;
1141 // During the destruction of all of our children, make sure we don't get
1142 // deleted.
1143 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1145 DestroyDirectManipulation();
1148 * On windows the LayerManagerOGL destructor wants the widget to be around for
1149 * cleanup. It also would like to have the HWND intact, so we nullptr it here.
1151 DestroyLayerManager();
1153 InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
1154 mDeviceNotifyHandle = nullptr;
1156 // The DestroyWindow function destroys the specified window. The function
1157 // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
1158 // and remove the keyboard focus from it. The function also destroys the
1159 // window's menu, flushes the thread message queue, destroys timers, removes
1160 // clipboard ownership, and breaks the clipboard viewer chain (if the window
1161 // is at the top of the viewer chain).
1163 // If the specified window is a parent or owner window, DestroyWindow
1164 // automatically destroys the associated child or owned windows when it
1165 // destroys the parent or owner window. The function first destroys child or
1166 // owned windows, and then it destroys the parent or owner window.
1167 VERIFY(::DestroyWindow(mWnd));
1169 // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
1170 // OnDestroy() didn't get called, call it now.
1171 if (false == mOnDestroyCalled) {
1172 MSGResult msgResult;
1173 mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
1174 OnDestroy();
1178 /**************************************************************
1180 * SECTION: Window class utilities
1182 * Utilities for calculating the proper window class name for
1183 * Create window.
1185 **************************************************************/
1187 /* static */
1188 const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
1189 UINT aExtraStyle, LPWSTR aIconID) {
1190 WNDCLASSW wc;
1191 if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
1192 // already registered
1193 return aClassName;
1196 wc.style = CS_DBLCLKS | aExtraStyle;
1197 wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
1198 wc.cbClsExtra = 0;
1199 wc.cbWndExtra = 0;
1200 wc.hInstance = nsToolkit::mDllInstance;
1201 wc.hIcon =
1202 aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
1203 wc.hCursor = nullptr;
1204 wc.hbrBackground = nullptr;
1205 wc.lpszMenuName = nullptr;
1206 wc.lpszClassName = aClassName;
1208 if (!::RegisterClassW(&wc)) {
1209 // For older versions of Win32 (i.e., not XP), the registration may
1210 // fail with aExtraStyle, so we have to re-register without it.
1211 wc.style = CS_DBLCLKS;
1212 ::RegisterClassW(&wc);
1214 return aClassName;
1217 static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
1219 /* static */
1220 const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType,
1221 bool aForMenupopupFrame) {
1222 MOZ_ASSERT_IF(aForMenupopupFrame, aWindowType == WindowType::Popup);
1223 switch (aWindowType) {
1224 case WindowType::Invisible:
1225 return RegisterWindowClass(kClassNameHidden, 0, gStockApplicationIcon);
1226 case WindowType::Dialog:
1227 return RegisterWindowClass(kClassNameDialog, 0, 0);
1228 case WindowType::Popup:
1229 if (aForMenupopupFrame) {
1230 return RegisterWindowClass(kClassNameDropShadow, CS_DROPSHADOW,
1231 gStockApplicationIcon);
1233 [[fallthrough]];
1234 default:
1235 return RegisterWindowClass(GetMainWindowClass(), 0,
1236 gStockApplicationIcon);
1240 /**************************************************************
1242 * SECTION: Window styles utilities
1244 * Return the proper windows styles and extended styles.
1246 **************************************************************/
1248 // Return nsWindow styles
1249 DWORD nsWindow::WindowStyle() {
1250 DWORD style;
1252 switch (mWindowType) {
1253 case WindowType::Child:
1254 style = WS_OVERLAPPED;
1255 break;
1257 case WindowType::Dialog:
1258 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
1259 DS_MODALFRAME | WS_CLIPCHILDREN;
1260 if (mBorderStyle != BorderStyle::Default)
1261 style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
1262 break;
1264 case WindowType::Popup:
1265 style = WS_POPUP | WS_OVERLAPPED;
1266 break;
1268 default:
1269 NS_ERROR("unknown border style");
1270 [[fallthrough]];
1272 case WindowType::TopLevel:
1273 case WindowType::Invisible:
1274 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
1275 WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
1276 break;
1279 if (mBorderStyle != BorderStyle::Default &&
1280 mBorderStyle != BorderStyle::All) {
1281 if (mBorderStyle == BorderStyle::None ||
1282 !(mBorderStyle & BorderStyle::Border))
1283 style &= ~WS_BORDER;
1285 if (mBorderStyle == BorderStyle::None ||
1286 !(mBorderStyle & BorderStyle::Title)) {
1287 style &= ~WS_DLGFRAME;
1290 if (mBorderStyle == BorderStyle::None ||
1291 !(mBorderStyle & BorderStyle::Close))
1292 style &= ~0;
1293 // XXX The close box can only be removed by changing the window class,
1294 // as far as I know --- roc+moz@cs.cmu.edu
1296 if (mBorderStyle == BorderStyle::None ||
1297 !(mBorderStyle & (BorderStyle::Menu | BorderStyle::Close)))
1298 style &= ~WS_SYSMENU;
1299 // Looks like getting rid of the system menu also does away with the
1300 // close box. So, we only get rid of the system menu if you want neither it
1301 // nor the close box. How does the Windows "Dialog" window class get just
1302 // closebox and no sysmenu? Who knows.
1304 if (mBorderStyle == BorderStyle::None ||
1305 !(mBorderStyle & BorderStyle::ResizeH))
1306 style &= ~WS_THICKFRAME;
1308 if (mBorderStyle == BorderStyle::None ||
1309 !(mBorderStyle & BorderStyle::Minimize))
1310 style &= ~WS_MINIMIZEBOX;
1312 if (mBorderStyle == BorderStyle::None ||
1313 !(mBorderStyle & BorderStyle::Maximize))
1314 style &= ~WS_MAXIMIZEBOX;
1316 if (IsPopupWithTitleBar()) {
1317 style |= WS_CAPTION;
1318 if (mBorderStyle & BorderStyle::Close) {
1319 style |= WS_SYSMENU;
1324 if (mIsChildWindow) {
1325 style |= WS_CLIPCHILDREN;
1326 if (!(style & WS_POPUP)) {
1327 style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
1331 VERIFY_WINDOW_STYLE(style);
1332 return style;
1335 // Return nsWindow extended styles
1336 DWORD nsWindow::WindowExStyle() {
1337 switch (mWindowType) {
1338 case WindowType::Child:
1339 return 0;
1341 case WindowType::Dialog:
1342 return WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME;
1344 case WindowType::Popup: {
1345 DWORD extendedStyle = WS_EX_TOOLWINDOW;
1346 if (mPopupLevel == PopupLevel::Top) extendedStyle |= WS_EX_TOPMOST;
1347 return extendedStyle;
1349 default:
1350 NS_ERROR("unknown border style");
1351 [[fallthrough]];
1353 case WindowType::TopLevel:
1354 case WindowType::Invisible:
1355 return WS_EX_WINDOWEDGE;
1359 /**************************************************************
1361 * SECTION: Native window association utilities
1363 * Used in Create and Destroy. A nsWindow can associate with its
1364 * underlying native window mWnd. Once a native window is
1365 * associated with a nsWindow, its native events will be handled
1366 * by the static member function nsWindow::WindowProc. Moreover,
1367 * the association will be registered in the WinUtils association
1368 * list, that is, calling WinUtils::GetNSWindowPtr on the native
1369 * window will return the associated nsWindow. This is used in
1370 * nsWindow::WindowProc to correctly dispatch native events to
1371 * the handler methods defined in nsWindow, even though it is a
1372 * static member function.
1374 * After dissociation, the native events of the native window will
1375 * no longer be handled by nsWindow::WindowProc, and will thus not
1376 * be dispatched to the nsWindow native event handler methods.
1377 * Moreover, the association will no longer be registered in the
1378 * WinUtils association list, so calling WinUtils::GetNSWindowPtr
1379 * on the native window will return nullptr.
1381 **************************************************************/
1383 bool nsWindow::AssociateWithNativeWindow() {
1384 if (!mWnd || !IsWindow(mWnd)) {
1385 NS_ERROR("Invalid window handle");
1386 return false;
1389 // Connect the this pointer to the native window handle.
1390 // This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
1391 // uses WinUtils::GetNSWindowPtr internally.
1392 WinUtils::SetNSWindowPtr(mWnd, this);
1394 ::SetLastError(ERROR_SUCCESS);
1395 const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1396 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
1397 if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
1398 NS_ERROR("Failure in SetWindowLongPtrW");
1399 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1400 return false;
1403 mPrevWndProc.emplace(prevWndProc);
1404 return true;
1407 void nsWindow::DissociateFromNativeWindow() {
1408 if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
1409 return;
1412 DebugOnly<WNDPROC> wndProcBeforeDissociate =
1413 reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1414 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
1415 NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
1416 "Unstacked an unexpected native window procedure");
1418 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1419 mPrevWndProc.reset();
1422 /**************************************************************
1424 * SECTION: nsIWidget::SetParent, nsIWidget::GetParent
1426 * Set or clear the parent widgets using window properties, and
1427 * handles calculating native parent handles.
1429 **************************************************************/
1431 // Get and set parent widgets
1432 void nsWindow::SetParent(nsIWidget* aNewParent) {
1433 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1434 nsIWidget* parent = GetParent();
1435 if (parent) {
1436 parent->RemoveChild(this);
1439 mParent = aNewParent;
1441 if (aNewParent) {
1442 ReparentNativeWidget(aNewParent);
1443 aNewParent->AddChild(this);
1444 return;
1446 if (mWnd) {
1447 // If we have no parent, SetParent should return the desktop.
1448 VERIFY(::SetParent(mWnd, nullptr));
1449 RecreateDirectManipulationIfNeeded();
1453 void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
1454 MOZ_ASSERT(aNewParent, "null widget");
1456 mParent = aNewParent;
1457 if (mWindowType == WindowType::Popup) {
1458 return;
1460 HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
1461 NS_ASSERTION(newParent, "Parent widget has a null native window handle");
1462 if (newParent && mWnd) {
1463 ::SetParent(mWnd, newParent);
1464 RecreateDirectManipulationIfNeeded();
1468 nsIWidget* nsWindow::GetParent(void) {
1469 if (mIsTopWidgetWindow) {
1470 return nullptr;
1472 if (mInDtor || mOnDestroyCalled) {
1473 return nullptr;
1475 return mParent;
1478 static int32_t RoundDown(double aDouble) {
1479 return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
1480 : static_cast<int32_t>(ceil(aDouble));
1483 float nsWindow::GetDPI() {
1484 float dpi = 96.0f;
1485 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
1486 if (screen) {
1487 screen->GetDpi(&dpi);
1489 return dpi;
1492 double nsWindow::GetDefaultScaleInternal() {
1493 if (mDefaultScale <= 0.0) {
1494 mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
1496 return mDefaultScale;
1499 int32_t nsWindow::LogToPhys(double aValue) {
1500 return WinUtils::LogToPhys(
1501 ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
1504 nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
1505 return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
1508 nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
1509 if (mIsTopWidgetWindow) {
1510 // Must use a flag instead of mWindowType to tell if the window is the
1511 // owned by the topmost widget, because a child window can be embedded
1512 // inside a HWND which is not associated with a nsIWidget.
1513 return nullptr;
1516 // If this widget has already been destroyed, pretend we have no parent.
1517 // This corresponds to code in Destroy which removes the destroyed
1518 // widget from its parent's child list.
1519 if (mInDtor || mOnDestroyCalled) return nullptr;
1521 // aIncludeOwner set to true implies walking the parent chain to retrieve the
1522 // root owner. aIncludeOwner set to false implies the search will stop at the
1523 // true parent (default).
1524 nsWindow* widget = nullptr;
1525 if (mWnd) {
1526 HWND parent = nullptr;
1527 if (aIncludeOwner)
1528 parent = ::GetParent(mWnd);
1529 else
1530 parent = ::GetAncestor(mWnd, GA_PARENT);
1532 if (parent) {
1533 widget = WinUtils::GetNSWindowPtr(parent);
1534 if (widget) {
1535 // If the widget is in the process of being destroyed then
1536 // do NOT return it
1537 if (widget->mInDtor) {
1538 widget = nullptr;
1544 return widget;
1547 /**************************************************************
1549 * SECTION: nsIWidget::Show
1551 * Hide or show this component.
1553 **************************************************************/
1555 void nsWindow::Show(bool bState) {
1556 if (bState && mIsShowingPreXULSkeletonUI) {
1557 // The first time we decide to actually show the window is when we decide
1558 // that we've taken over the window from the skeleton UI, and we should
1559 // no longer treat resizes / moves specially.
1560 mIsShowingPreXULSkeletonUI = false;
1561 #if defined(ACCESSIBILITY)
1562 // If our HWND has focus and the a11y engine hasn't started yet, fire a
1563 // focus win event. Windows already did this when the skeleton UI appeared,
1564 // but a11y wouldn't have been able to start at that point even if a client
1565 // responded. Firing this now gives clients the chance to respond with
1566 // WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
1567 // this if the a11y engine has already started because it has probably
1568 // already fired focus on a descendant.
1569 if (::GetFocus() == mWnd && !GetAccService()) {
1570 ::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
1572 #endif // defined(ACCESSIBILITY)
1575 if (mForMenupopupFrame) {
1576 MOZ_ASSERT(ChooseWindowClass(mWindowType, mForMenupopupFrame) ==
1577 kClassNameDropShadow);
1578 const bool shouldUseDropShadow =
1579 mTransparencyMode != TransparencyMode::Transparent;
1581 static bool sShadowEnabled = true;
1582 if (sShadowEnabled != shouldUseDropShadow) {
1583 ::SetClassLongA(mWnd, GCL_STYLE, shouldUseDropShadow ? CS_DROPSHADOW : 0);
1584 sShadowEnabled = shouldUseDropShadow;
1587 // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
1588 // some popup menus to become invisible.
1589 LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
1590 if (exStyle & WS_EX_LAYERED) {
1591 ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
1595 bool syncInvalidate = false;
1597 bool wasVisible = mIsVisible;
1598 // Set the status now so that anyone asking during ShowWindow or
1599 // SetWindowPos would get the correct answer.
1600 mIsVisible = bState;
1602 // We may have cached an out of date visible state. This can happen
1603 // when session restore sets the full screen mode.
1604 if (mIsVisible)
1605 mOldStyle |= WS_VISIBLE;
1606 else
1607 mOldStyle &= ~WS_VISIBLE;
1609 if (mWnd) {
1610 if (bState) {
1611 if (!wasVisible && mWindowType == WindowType::TopLevel) {
1612 // speed up the initial paint after show for
1613 // top level windows:
1614 syncInvalidate = true;
1616 // Set the cursor before showing the window to avoid the default wait
1617 // cursor.
1618 SetCursor(Cursor{eCursor_standard});
1620 switch (mFrameState->GetSizeMode()) {
1621 case nsSizeMode_Fullscreen:
1622 ::ShowWindow(mWnd, SW_SHOW);
1623 break;
1624 case nsSizeMode_Maximized:
1625 ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
1626 break;
1627 case nsSizeMode_Minimized:
1628 ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
1629 break;
1630 default:
1631 if (CanTakeFocus() && !mAlwaysOnTop) {
1632 ::ShowWindow(mWnd, SW_SHOWNORMAL);
1633 } else {
1634 ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
1635 // Don't flicker the window if we're restoring session
1636 if (!sIsRestoringSession) {
1637 Unused << GetAttention(2);
1640 break;
1642 } else {
1643 DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
1644 if (wasVisible) flags |= SWP_NOZORDER;
1645 if (mAlwaysOnTop) flags |= SWP_NOACTIVATE;
1647 if (mWindowType == WindowType::Popup) {
1648 // ensure popups are the topmost of the TOPMOST
1649 // layer. Remember not to set the SWP_NOZORDER
1650 // flag as that might allow the taskbar to overlap
1651 // the popup.
1652 flags |= SWP_NOACTIVATE;
1653 HWND owner = ::GetWindow(mWnd, GW_OWNER);
1654 if (owner) {
1655 // PopupLevel::Top popups should be above all else. All other
1656 // types should be placed in front of their owner, without
1657 // changing the owner's z-level relative to other windows.
1658 if (mPopupLevel != PopupLevel::Top) {
1659 ::SetWindowPos(mWnd, owner, 0, 0, 0, 0, flags);
1660 ::SetWindowPos(owner, mWnd, 0, 0, 0, 0,
1661 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1662 } else {
1663 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1665 } else {
1666 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
1668 } else {
1669 if (mWindowType == WindowType::Dialog && !CanTakeFocus())
1670 flags |= SWP_NOACTIVATE;
1672 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1675 } else {
1676 // Clear contents to avoid ghosting of old content if we display
1677 // this window again.
1678 if (wasVisible && mTransparencyMode == TransparencyMode::Transparent) {
1679 if (mCompositorWidgetDelegate) {
1680 mCompositorWidgetDelegate->ClearTransparentWindow();
1683 if (mWindowType != WindowType::Dialog) {
1684 ::ShowWindow(mWnd, SW_HIDE);
1685 } else {
1686 ::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
1687 SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
1688 SWP_NOACTIVATE);
1693 if (!wasVisible && bState) {
1694 Invalidate();
1695 if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
1696 ::UpdateWindow(mWnd);
1700 if (mOpeningAnimationSuppressed) {
1701 SuppressAnimation(false);
1705 /**************************************************************
1707 * SECTION: nsIWidget::IsVisible
1709 * Returns the visibility state.
1711 **************************************************************/
1713 // Return true if the component is visible, false otherwise.
1715 // This does not take cloaking into account.
1716 bool nsWindow::IsVisible() const { return mIsVisible; }
1718 /**************************************************************
1720 * SECTION: Window clipping utilities
1722 * Used in Size and Move operations for setting the proper
1723 * window clipping regions for window transparency.
1725 **************************************************************/
1727 // XP and Vista visual styles sometimes require window clipping regions to be
1728 // applied for proper transparency. These routines are called on size and move
1729 // operations.
1730 // XXX this is apparently still needed in Windows 7 and later
1731 void nsWindow::ClearThemeRegion() {
1732 if (mWindowType == WindowType::Popup && !IsPopupWithTitleBar() &&
1733 (mPopupType == PopupType::Tooltip || mPopupType == PopupType::Panel)) {
1734 SetWindowRgn(mWnd, nullptr, false);
1738 /**************************************************************
1740 * SECTION: Touch and APZ-related functions
1742 **************************************************************/
1744 void nsWindow::RegisterTouchWindow() {
1745 mTouchWindow = true;
1746 ::RegisterTouchWindow(mWnd, TWF_WANTPALM);
1747 ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
1750 BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
1751 nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
1752 if (win) {
1753 ::RegisterTouchWindow(aWnd, TWF_WANTPALM);
1755 return TRUE;
1758 void nsWindow::LockAspectRatio(bool aShouldLock) {
1759 if (aShouldLock) {
1760 mAspectRatio = (float)mBounds.Width() / (float)mBounds.Height();
1761 } else {
1762 mAspectRatio = 0.0;
1766 /**************************************************************
1768 * SECTION: nsIWidget::SetInputRegion
1770 * Sets whether the window should ignore mouse events.
1772 **************************************************************/
1773 void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
1774 mInputRegion = aInputRegion;
1777 /**************************************************************
1779 * SECTION: nsIWidget::Move, nsIWidget::Resize, nsIWidget::Size
1781 * Repositioning and sizing a window.
1783 **************************************************************/
1785 void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1786 SizeConstraints c = aConstraints;
1788 if (mWindowType != WindowType::Popup && mResizable) {
1789 c.mMinSize.width =
1790 std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
1791 c.mMinSize.height =
1792 std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
1795 if (mMaxTextureSize > 0) {
1796 // We can't make ThebesLayers bigger than this anyway.. no point it letting
1797 // a window grow bigger as we won't be able to draw content there in
1798 // general.
1799 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
1800 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
1803 mSizeConstraintsScale = GetDefaultScale().scale;
1805 nsBaseWidget::SetSizeConstraints(c);
1808 const SizeConstraints nsWindow::GetSizeConstraints() {
1809 double scale = GetDefaultScale().scale;
1810 if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
1811 return mSizeConstraints;
1813 scale /= mSizeConstraintsScale;
1814 SizeConstraints c = mSizeConstraints;
1815 if (c.mMinSize.width != NS_MAXSIZE) {
1816 c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
1818 if (c.mMinSize.height != NS_MAXSIZE) {
1819 c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
1821 if (c.mMaxSize.width != NS_MAXSIZE) {
1822 c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
1824 if (c.mMaxSize.height != NS_MAXSIZE) {
1825 c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
1827 return c;
1830 // Move this component
1831 void nsWindow::Move(double aX, double aY) {
1832 if (mWindowType == WindowType::TopLevel ||
1833 mWindowType == WindowType::Dialog) {
1834 SetSizeMode(nsSizeMode_Normal);
1837 // for top-level windows only, convert coordinates from desktop pixels
1838 // (the "parent" coordinate space) to the window's device pixel space
1839 double scale =
1840 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1841 int32_t x = NSToIntRound(aX * scale);
1842 int32_t y = NSToIntRound(aY * scale);
1844 // Check to see if window needs to be moved first
1845 // to avoid a costly call to SetWindowPos. This check
1846 // can not be moved to the calling code in nsView, because
1847 // some platforms do not position child windows correctly
1849 // Only perform this check for non-popup windows, since the positioning can
1850 // in fact change even when the x/y do not. We always need to perform the
1851 // check. See bug #97805 for details.
1852 if (mWindowType != WindowType::Popup && mBounds.IsEqualXY(x, y)) {
1853 // Nothing to do, since it is already positioned correctly.
1854 return;
1857 mBounds.MoveTo(x, y);
1859 if (mWnd) {
1860 #ifdef DEBUG
1861 // complain if a window is moved offscreen (legal, but potentially
1862 // worrisome)
1863 if (mIsTopWidgetWindow) { // only a problem for top-level windows
1864 // Make sure this window is actually on the screen before we move it
1865 // XXX: Needs multiple monitor support
1866 HDC dc = ::GetDC(mWnd);
1867 if (dc) {
1868 if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
1869 RECT workArea;
1870 ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
1871 // no annoying assertions. just mention the issue.
1872 if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) {
1873 MOZ_LOG(gWindowsLog, LogLevel::Info,
1874 ("window moved to offscreen position\n"));
1877 ::ReleaseDC(mWnd, dc);
1880 #endif
1882 // Normally, when the skeleton UI is disabled, we resize+move the window
1883 // before showing it in order to ensure that it restores to the correct
1884 // position when the user un-maximizes it. However, when we are using the
1885 // skeleton UI, this results in the skeleton UI window being moved around
1886 // undesirably before being locked back into the maximized position. To
1887 // avoid this, we simply set the placement to restore to via
1888 // SetWindowPlacement. It's a little bit more of a dance, though, since we
1889 // need to convert the workspace coords that SetWindowPlacement uses to the
1890 // screen space coordinates we normally use with SetWindowPos.
1891 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1892 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1893 VERIFY(::GetWindowPlacement(mWnd, &pl));
1895 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
1896 if (NS_WARN_IF(!monitor)) {
1897 return;
1899 MONITORINFO mi = {sizeof(MONITORINFO)};
1900 VERIFY(::GetMonitorInfo(monitor, &mi));
1902 int32_t deltaX =
1903 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
1904 int32_t deltaY =
1905 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
1906 pl.rcNormalPosition.left += deltaX;
1907 pl.rcNormalPosition.right += deltaX;
1908 pl.rcNormalPosition.top += deltaY;
1909 pl.rcNormalPosition.bottom += deltaY;
1910 VERIFY(::SetWindowPlacement(mWnd, &pl));
1911 } else {
1912 ClearThemeRegion();
1914 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
1915 double oldScale = mDefaultScale;
1916 mResizeState = IN_SIZEMOVE;
1917 VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
1918 mResizeState = NOT_RESIZING;
1919 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
1920 ChangedDPI();
1924 ResizeDirectManipulationViewport();
1928 // Resize this component
1929 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1930 // for top-level windows only, convert coordinates from desktop pixels
1931 // (the "parent" coordinate space) to the window's device pixel space
1932 double scale =
1933 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1934 int32_t width = NSToIntRound(aWidth * scale);
1935 int32_t height = NSToIntRound(aHeight * scale);
1937 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
1938 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
1939 if (width < 0 || height < 0) {
1940 gfxCriticalNoteOnce << "Negative passed to Resize(" << width << ", "
1941 << height << ") repaint: " << aRepaint;
1944 ConstrainSize(&width, &height);
1946 // Avoid unnecessary resizing calls
1947 if (mBounds.IsEqualSize(width, height)) {
1948 if (aRepaint) {
1949 Invalidate();
1951 return;
1954 // Set cached value for lightweight and printing
1955 bool wasLocking = mAspectRatio != 0.0;
1956 mBounds.SizeTo(width, height);
1957 if (wasLocking) {
1958 LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
1961 if (mWnd) {
1962 // Refer to the comment above a similar check in nsWindow::Move
1963 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1964 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1965 VERIFY(::GetWindowPlacement(mWnd, &pl));
1966 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
1967 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
1968 mResizeState = RESIZING;
1969 VERIFY(::SetWindowPlacement(mWnd, &pl));
1970 mResizeState = NOT_RESIZING;
1971 } else {
1972 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
1974 if (!aRepaint) {
1975 flags |= SWP_NOREDRAW;
1978 ClearThemeRegion();
1979 double oldScale = mDefaultScale;
1980 mResizeState = RESIZING;
1981 VERIFY(
1982 ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags));
1984 mResizeState = NOT_RESIZING;
1985 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
1986 ChangedDPI();
1990 ResizeDirectManipulationViewport();
1993 if (aRepaint) Invalidate();
1996 // Resize this component
1997 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
1998 bool aRepaint) {
1999 // for top-level windows only, convert coordinates from desktop pixels
2000 // (the "parent" coordinate space) to the window's device pixel space
2001 double scale =
2002 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
2003 int32_t x = NSToIntRound(aX * scale);
2004 int32_t y = NSToIntRound(aY * scale);
2005 int32_t width = NSToIntRound(aWidth * scale);
2006 int32_t height = NSToIntRound(aHeight * scale);
2008 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
2009 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
2010 if (width < 0 || height < 0) {
2011 gfxCriticalNoteOnce << "Negative passed to Resize(" << x << " ," << y
2012 << ", " << width << ", " << height
2013 << ") repaint: " << aRepaint;
2016 ConstrainSize(&width, &height);
2018 // Avoid unnecessary resizing calls
2019 if (mBounds.IsEqualRect(x, y, width, height)) {
2020 if (aRepaint) {
2021 Invalidate();
2023 return;
2026 // Set cached value for lightweight and printing
2027 mBounds.SetRect(x, y, width, height);
2029 if (mWnd) {
2030 // Refer to the comment above a similar check in nsWindow::Move
2031 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2032 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2033 VERIFY(::GetWindowPlacement(mWnd, &pl));
2035 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2036 if (NS_WARN_IF(!monitor)) {
2037 return;
2039 MONITORINFO mi = {sizeof(MONITORINFO)};
2040 VERIFY(::GetMonitorInfo(monitor, &mi));
2042 int32_t deltaX =
2043 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
2044 int32_t deltaY =
2045 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
2046 pl.rcNormalPosition.left += deltaX;
2047 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
2048 pl.rcNormalPosition.top += deltaY;
2049 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
2050 VERIFY(::SetWindowPlacement(mWnd, &pl));
2051 } else {
2052 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
2053 if (!aRepaint) {
2054 flags |= SWP_NOREDRAW;
2057 ClearThemeRegion();
2059 double oldScale = mDefaultScale;
2060 mResizeState = RESIZING;
2061 VERIFY(
2062 ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags));
2063 mResizeState = NOT_RESIZING;
2064 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
2065 ChangedDPI();
2068 if (mTransitionWnd) {
2069 // If we have a fullscreen transition window, we need to make
2070 // it topmost again, otherwise the taskbar may be raised by
2071 // the system unexpectedly when we leave fullscreen state.
2072 ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
2073 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
2077 ResizeDirectManipulationViewport();
2080 if (aRepaint) Invalidate();
2083 mozilla::Maybe<bool> nsWindow::IsResizingNativeWidget() {
2084 if (mResizeState == RESIZING) {
2085 return Some(true);
2087 return Some(false);
2090 /**************************************************************
2092 * SECTION: Window Z-order and state.
2094 * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode,
2095 * nsIWidget::ConstrainPosition
2097 * Z-order, positioning, restore, minimize, and maximize.
2099 **************************************************************/
2101 // Position the window behind the given window
2102 void nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
2103 nsIWidget* aWidget, bool aActivate) {
2104 HWND behind = HWND_TOP;
2105 if (aPlacement == eZPlacementBottom)
2106 behind = HWND_BOTTOM;
2107 else if (aPlacement == eZPlacementBelow && aWidget)
2108 behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
2109 UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE;
2110 if (!aActivate) flags |= SWP_NOACTIVATE;
2112 if (!CanTakeFocus() && behind == HWND_TOP) {
2113 // Can't place the window to top so place it behind the foreground window
2114 // (as long as it is not topmost)
2115 HWND wndAfter = ::GetForegroundWindow();
2116 if (!wndAfter)
2117 behind = HWND_BOTTOM;
2118 else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST))
2119 behind = wndAfter;
2120 flags |= SWP_NOACTIVATE;
2123 ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
2126 static UINT GetCurrentShowCmd(HWND aWnd) {
2127 WINDOWPLACEMENT pl;
2128 pl.length = sizeof(pl);
2129 ::GetWindowPlacement(aWnd, &pl);
2130 return pl.showCmd;
2133 // Maximize, minimize or restore the window.
2134 void nsWindow::SetSizeMode(nsSizeMode aMode) {
2135 // If we are still displaying a maximized pre-XUL skeleton UI, ignore the
2136 // noise of sizemode changes. Once we have "shown" the window for the first
2137 // time (called nsWindow::Show(true), even though the window is already
2138 // technically displayed), we will again accept sizemode changes.
2139 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2140 return;
2143 mFrameState->EnsureSizeMode(aMode);
2146 nsSizeMode nsWindow::SizeMode() { return mFrameState->GetSizeMode(); }
2148 void DoGetWorkspaceID(HWND aWnd, nsAString* aWorkspaceID) {
2149 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2150 if (!desktopManager || !aWnd) {
2151 return;
2154 GUID desktop;
2155 HRESULT hr = desktopManager->GetWindowDesktopId(aWnd, &desktop);
2156 if (FAILED(hr)) {
2157 return;
2160 RPC_WSTR workspaceIDStr = nullptr;
2161 if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
2162 aWorkspaceID->Assign((wchar_t*)workspaceIDStr);
2163 RpcStringFreeW(&workspaceIDStr);
2167 void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
2168 // If we have a value cached, use that, but also make sure it is
2169 // scheduled to be updated. If we don't yet have a value, get
2170 // one synchronously.
2171 auto desktop = mDesktopId.Lock();
2172 if (desktop->mID.IsEmpty()) {
2173 DoGetWorkspaceID(mWnd, &desktop->mID);
2174 desktop->mUpdateIsQueued = false;
2175 } else {
2176 AsyncUpdateWorkspaceID(*desktop);
2179 workspaceID = desktop->mID;
2182 void nsWindow::AsyncUpdateWorkspaceID(Desktop& aDesktop) {
2183 struct UpdateWorkspaceIdTask : public Task {
2184 explicit UpdateWorkspaceIdTask(nsWindow* aSelf)
2185 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
2186 mSelf(aSelf) {}
2188 TaskResult Run() override {
2189 auto desktop = mSelf->mDesktopId.Lock();
2190 if (desktop->mUpdateIsQueued) {
2191 DoGetWorkspaceID(mSelf->mWnd, &desktop->mID);
2192 desktop->mUpdateIsQueued = false;
2194 return TaskResult::Complete;
2197 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
2198 bool GetName(nsACString& aName) override {
2199 aName.AssignLiteral("UpdateWorkspaceIdTask");
2200 return true;
2202 #endif
2204 RefPtr<nsWindow> mSelf;
2207 if (aDesktop.mUpdateIsQueued) {
2208 return;
2211 aDesktop.mUpdateIsQueued = true;
2212 TaskController::Get()->AddTask(MakeAndAddRef<UpdateWorkspaceIdTask>(this));
2215 void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
2216 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2217 if (!desktopManager) {
2218 return;
2221 GUID desktop;
2222 const nsString flat = PromiseFlatString(workspaceID);
2223 RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
2224 if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
2225 if (SUCCEEDED(desktopManager->MoveWindowToDesktop(mWnd, desktop))) {
2226 auto desktop = mDesktopId.Lock();
2227 desktop->mID = workspaceID;
2232 void nsWindow::SuppressAnimation(bool aSuppress) {
2233 DWORD dwAttribute = aSuppress ? TRUE : FALSE;
2234 DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
2235 sizeof dwAttribute);
2238 // Constrain a potential move to fit onscreen
2239 // Position (aX, aY) is specified in Windows screen (logical) pixels,
2240 // except when using per-monitor DPI, in which case it's device pixels.
2241 void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
2242 if (!mIsTopWidgetWindow) // only a problem for top-level windows
2243 return;
2245 double dpiScale = GetDesktopToDeviceScale().scale;
2247 // We need to use the window size in the kind of pixels used for window-
2248 // manipulation APIs.
2249 int32_t logWidth =
2250 std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
2251 int32_t logHeight =
2252 std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
2254 /* get our playing field. use the current screen, or failing that
2255 for any reason, use device caps for the default screen. */
2256 RECT screenRect;
2258 nsCOMPtr<nsIScreenManager> screenmgr =
2259 do_GetService(sScreenManagerContractID);
2260 if (!screenmgr) {
2261 return;
2263 nsCOMPtr<nsIScreen> screen;
2264 int32_t left, top, width, height;
2266 screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
2267 getter_AddRefs(screen));
2268 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
2269 // For normalized windows, use the desktop work area.
2270 nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
2271 if (NS_FAILED(rv)) {
2272 return;
2274 } else {
2275 // For full screen windows, use the desktop.
2276 nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height);
2277 if (NS_FAILED(rv)) {
2278 return;
2281 screenRect.left = left;
2282 screenRect.right = left + width;
2283 screenRect.top = top;
2284 screenRect.bottom = top + height;
2286 if (aPoint.x < screenRect.left)
2287 aPoint.x = screenRect.left;
2288 else if (aPoint.x >= screenRect.right - logWidth)
2289 aPoint.x = screenRect.right - logWidth;
2291 if (aPoint.y < screenRect.top)
2292 aPoint.y = screenRect.top;
2293 else if (aPoint.y >= screenRect.bottom - logHeight)
2294 aPoint.y = screenRect.bottom - logHeight;
2297 /**************************************************************
2299 * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
2301 * Enabling and disabling the widget.
2303 **************************************************************/
2305 // Enable/disable this component
2306 void nsWindow::Enable(bool bState) {
2307 if (mWnd) {
2308 ::EnableWindow(mWnd, bState);
2312 // Return the current enable state
2313 bool nsWindow::IsEnabled() const {
2314 return !mWnd || (::IsWindowEnabled(mWnd) &&
2315 ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
2318 /**************************************************************
2320 * SECTION: nsIWidget::SetFocus
2322 * Give the focus to this widget.
2324 **************************************************************/
2326 void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2327 if (mWnd) {
2328 #ifdef WINSTATE_DEBUG_OUTPUT
2329 if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
2330 MOZ_LOG(gWindowsLog, LogLevel::Info,
2331 ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
2332 } else {
2333 MOZ_LOG(gWindowsLog, LogLevel::Info,
2334 ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
2336 #endif
2337 // Uniconify, if necessary
2338 HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
2339 if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
2340 ::ShowWindow(toplevelWnd, SW_RESTORE);
2342 ::SetFocus(mWnd);
2346 /**************************************************************
2348 * SECTION: Bounds
2350 * GetBounds, GetClientBounds, GetScreenBounds,
2351 * GetRestoredBounds, GetClientOffset, SetNonClientMargins
2353 * Bound calculations.
2355 **************************************************************/
2357 // Return the window's full dimensions in screen coordinates.
2358 // If the window has a parent, converts the origin to an offset
2359 // of the parent's screen origin.
2360 LayoutDeviceIntRect nsWindow::GetBounds() {
2361 if (!mWnd) {
2362 return mBounds;
2365 RECT r;
2366 VERIFY(::GetWindowRect(mWnd, &r));
2368 LayoutDeviceIntRect rect;
2370 // assign size
2371 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2373 // popup window bounds' are in screen coordinates, not relative to parent
2374 // window
2375 if (mWindowType == WindowType::Popup) {
2376 rect.MoveTo(r.left, r.top);
2377 return rect;
2380 // chrome on parent:
2381 // ___ 5,5 (chrome start)
2382 // | ____ 10,10 (client start)
2383 // | | ____ 20,20 (child start)
2384 // | | |
2385 // 20,20 - 5,5 = 15,15 (??)
2386 // minus GetClientOffset:
2387 // 15,15 - 5,5 = 10,10
2389 // no chrome on parent:
2390 // ______ 10,10 (win start)
2391 // | ____ 20,20 (child start)
2392 // | |
2393 // 20,20 - 10,10 = 10,10
2395 // walking the chain:
2396 // ___ 5,5 (chrome start)
2397 // | ___ 10,10 (client start)
2398 // | | ___ 20,20 (child start)
2399 // | | | __ 30,30 (child start)
2400 // | | | |
2401 // 30,30 - 20,20 = 10,10 (offset from second child to first)
2402 // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
2403 // minus GetClientOffset:
2404 // 25,25 - 5,5 = 20,20 (offset from second child to parent client)
2406 // convert coordinates if parent exists
2407 HWND parent = ::GetParent(mWnd);
2408 if (parent) {
2409 RECT pr;
2410 VERIFY(::GetWindowRect(parent, &pr));
2411 r.left -= pr.left;
2412 r.top -= pr.top;
2413 // adjust for chrome
2414 nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
2415 if (pWidget && pWidget->IsTopLevelWidget()) {
2416 LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
2417 r.left -= clientOffset.x;
2418 r.top -= clientOffset.y;
2421 rect.MoveTo(r.left, r.top);
2422 if (mCompositorSession &&
2423 !wr::WindowSizeSanityCheck(rect.width, rect.height)) {
2424 gfxCriticalNoteOnce << "Invalid size" << rect << " size mode "
2425 << mFrameState->GetSizeMode();
2428 return rect;
2431 // Get this component dimension
2432 LayoutDeviceIntRect nsWindow::GetClientBounds() {
2433 if (!mWnd) {
2434 return LayoutDeviceIntRect(0, 0, 0, 0);
2437 RECT r;
2438 if (!::GetClientRect(mWnd, &r)) {
2439 MOZ_ASSERT_UNREACHABLE("unexpected to be called");
2440 gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
2441 return mBounds;
2444 LayoutDeviceIntRect bounds = GetBounds();
2445 LayoutDeviceIntRect rect;
2446 rect.MoveTo(bounds.TopLeft() + GetClientOffset());
2447 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2448 return rect;
2451 // Like GetBounds, but don't offset by the parent
2452 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
2453 if (!mWnd) {
2454 return mBounds;
2457 RECT r;
2458 VERIFY(::GetWindowRect(mWnd, &r));
2460 return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2463 nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
2464 if (SizeMode() == nsSizeMode_Normal) {
2465 aRect = GetScreenBounds();
2466 return NS_OK;
2468 if (!mWnd) {
2469 return NS_ERROR_FAILURE;
2472 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2473 VERIFY(::GetWindowPlacement(mWnd, &pl));
2474 const RECT& r = pl.rcNormalPosition;
2476 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2477 if (!monitor) {
2478 return NS_ERROR_FAILURE;
2480 MONITORINFO mi = {sizeof(MONITORINFO)};
2481 VERIFY(::GetMonitorInfo(monitor, &mi));
2483 aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2484 aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
2485 mi.rcWork.top - mi.rcMonitor.top);
2486 return NS_OK;
2489 // Return the x,y offset of the client area from the origin of the window. If
2490 // the window is borderless returns (0,0).
2491 LayoutDeviceIntPoint nsWindow::GetClientOffset() {
2492 if (!mWnd) {
2493 return LayoutDeviceIntPoint(0, 0);
2496 RECT r1;
2497 GetWindowRect(mWnd, &r1);
2498 LayoutDeviceIntPoint pt = WidgetToScreenOffset();
2499 return LayoutDeviceIntPoint(pt.x - LayoutDeviceIntCoord(r1.left),
2500 pt.y - LayoutDeviceIntCoord(r1.top));
2503 void nsWindow::ResetLayout() {
2504 // This will trigger a frame changed event, triggering
2505 // nc calc size and a sizemode gecko event.
2506 SetWindowPos(mWnd, 0, 0, 0, 0, 0,
2507 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
2508 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
2510 // If hidden, just send the frame changed event for now.
2511 if (!mIsVisible) {
2512 return;
2515 // Send a gecko size event to trigger reflow.
2516 RECT clientRc = {0};
2517 GetClientRect(mWnd, &clientRc);
2518 OnResize(WinUtils::ToIntRect(clientRc).Size());
2520 // Invalidate and update
2521 Invalidate();
2524 // Internally track the caption status via a window property. Required
2525 // due to our internal handling of WM_NCACTIVATE when custom client
2526 // margins are set.
2527 static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty";
2528 typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
2529 static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr>
2530 sGetWindowInfoPtrStub;
2532 BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) {
2533 if (!sGetWindowInfoPtrStub) {
2534 NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!");
2535 return FALSE;
2537 int windowStatus =
2538 reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty));
2539 // No property set, return the default data.
2540 if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi);
2541 // Call GetWindowInfo and update dwWindowStatus with our
2542 // internally tracked value.
2543 BOOL result = sGetWindowInfoPtrStub(hWnd, pwi);
2544 if (result && pwi)
2545 pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION);
2546 return result;
2549 void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) {
2550 if (!mWnd) return;
2552 sUser32Intercept.Init("user32.dll");
2553 sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo",
2554 &GetWindowInfoHook);
2555 if (!sGetWindowInfoPtrStub) {
2556 return;
2559 // Update our internally tracked caption status
2560 SetPropW(mWnd, kManageWindowInfoProperty,
2561 reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
2564 #define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
2565 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
2567 void nsWindow::UpdateDarkModeToolbar() {
2568 LookAndFeel::EnsureColorSchemesInitialized();
2569 BOOL dark = LookAndFeel::ColorSchemeForChrome() == ColorScheme::Dark;
2570 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &dark,
2571 sizeof dark);
2572 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
2573 sizeof dark);
2576 LayoutDeviceIntMargin nsWindow::NormalWindowNonClientOffset() const {
2577 LayoutDeviceIntMargin nonClientOffset;
2579 // We're dealing with a "normal" window (not maximized, minimized, or
2580 // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset`
2581 // accordingly.
2583 // Setting `mNonClientOffset` to 0 has the effect of leaving the default
2584 // frame intact. Setting it to a value greater than 0 reduces the frame
2585 // size by that amount.
2587 if (mNonClientMargins.top > 0) {
2588 nonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
2589 } else if (mNonClientMargins.top == 0) {
2590 nonClientOffset.top = mCaptionHeight;
2591 } else {
2592 nonClientOffset.top = 0;
2595 if (mNonClientMargins.bottom > 0) {
2596 nonClientOffset.bottom =
2597 std::min(mVertResizeMargin, mNonClientMargins.bottom);
2598 } else if (mNonClientMargins.bottom == 0) {
2599 nonClientOffset.bottom = mVertResizeMargin;
2600 } else {
2601 nonClientOffset.bottom = 0;
2604 if (mNonClientMargins.left > 0) {
2605 nonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left);
2606 } else if (mNonClientMargins.left == 0) {
2607 nonClientOffset.left = mHorResizeMargin;
2608 } else {
2609 nonClientOffset.left = 0;
2612 if (mNonClientMargins.right > 0) {
2613 nonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right);
2614 } else if (mNonClientMargins.right == 0) {
2615 nonClientOffset.right = mHorResizeMargin;
2616 } else {
2617 nonClientOffset.right = 0;
2619 return nonClientOffset;
2623 * Called when the window layout changes: full screen mode transitions,
2624 * theme changes, and composition changes. Calculates the new non-client
2625 * margins and fires off a frame changed event, which triggers an nc calc
2626 * size windows event, kicking the changes in.
2628 * The offsets calculated here are based on the value of `mNonClientMargins`
2629 * which is specified in the "chromemargins" attribute of the window. For
2630 * each margin, the value specified has the following meaning:
2631 * -1 - leave the default frame in place
2632 * 0 - remove the frame
2633 * >0 - frame size equals min(0, (default frame size - margin value))
2635 * This function calculates and populates `mNonClientOffset`.
2636 * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
2637 * as (default frame size - offset). For example, if the left frame should
2638 * be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
2639 * will equal 1.
2641 * For maximized, fullscreen, and minimized windows, the values stored in
2642 * `mNonClientMargins` are ignored, and special processing takes place.
2644 * For non-glass windows, we only allow frames to be their default size
2645 * or removed entirely.
2647 bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) {
2648 if (!mCustomNonClient) {
2649 return false;
2652 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
2654 bool hasCaption =
2655 bool(mBorderStyle & (BorderStyle::All | BorderStyle::Title |
2656 BorderStyle::Menu | BorderStyle::Default));
2658 float dpi = GetDPI();
2660 // mCaptionHeight is the default size of the NC area at
2661 // the top of the window. If the window has a caption,
2662 // the size is calculated as the sum of:
2663 // SM_CYFRAME - The thickness of the sizing border
2664 // around a resizable window
2665 // SM_CXPADDEDBORDER - The amount of border padding
2666 // for captioned windows
2667 // SM_CYCAPTION - The height of the caption area
2669 // If the window does not have a caption, mCaptionHeight will be equal to
2670 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2671 mCaptionHeight =
2672 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2673 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +
2674 WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2675 : 0);
2676 if (!mUseResizeMarginOverrides) {
2677 // mHorResizeMargin is the size of the default NC areas on the
2678 // left and right sides of our window. It is calculated as
2679 // the sum of:
2680 // SM_CXFRAME - The thickness of the sizing border
2681 // SM_CXPADDEDBORDER - The amount of border padding
2682 // for captioned windows
2684 // If the window does not have a caption, mHorResizeMargin will be equal to
2685 // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
2686 mHorResizeMargin =
2687 WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
2688 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2689 : 0);
2691 // mVertResizeMargin is the size of the default NC area at the
2692 // bottom of the window. It is calculated as the sum of:
2693 // SM_CYFRAME - The thickness of the sizing border
2694 // SM_CXPADDEDBORDER - The amount of border padding
2695 // for captioned windows.
2697 // If the window does not have a caption, mVertResizeMargin will be equal to
2698 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2699 mVertResizeMargin =
2700 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2701 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2702 : 0);
2705 if (sizeMode == nsSizeMode_Minimized) {
2706 // Use default frame size for minimized windows
2707 mNonClientOffset.top = 0;
2708 mNonClientOffset.left = 0;
2709 mNonClientOffset.right = 0;
2710 mNonClientOffset.bottom = 0;
2711 } else if (sizeMode == nsSizeMode_Fullscreen) {
2712 // Remove the default frame from the top of our fullscreen window. This
2713 // makes the whole caption part of our client area, allowing us to draw
2714 // in the whole caption area. Additionally remove the default frame from
2715 // the left, right, and bottom.
2716 mNonClientOffset.top = mCaptionHeight;
2717 mNonClientOffset.bottom = mVertResizeMargin;
2718 mNonClientOffset.left = mHorResizeMargin;
2719 mNonClientOffset.right = mHorResizeMargin;
2720 } else if (sizeMode == nsSizeMode_Maximized) {
2721 // We make the entire frame part of the client area. We leave the default
2722 // frame sizes for left, right and bottom since Windows will automagically
2723 // position the edges "offscreen" for maximized windows.
2724 int verticalResize =
2725 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2726 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2727 : 0);
2729 mNonClientOffset.top = mCaptionHeight - verticalResize;
2730 mNonClientOffset.bottom = 0;
2731 mNonClientOffset.left = 0;
2732 mNonClientOffset.right = 0;
2734 mozilla::Maybe<UINT> maybeEdge = GetHiddenTaskbarEdge();
2735 if (maybeEdge) {
2736 auto edge = maybeEdge.value();
2737 if (ABE_LEFT == edge) {
2738 mNonClientOffset.left -= kHiddenTaskbarSize;
2739 } else if (ABE_RIGHT == edge) {
2740 mNonClientOffset.right -= kHiddenTaskbarSize;
2741 } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
2742 mNonClientOffset.bottom -= kHiddenTaskbarSize;
2745 // When we are drawing the non-client region, we need
2746 // to clear the portion of the NC region that is exposed by the
2747 // hidden taskbar. As above, we clear the bottom of the NC region
2748 // when the taskbar is at the top of the screen.
2749 UINT clearEdge = (edge == ABE_TOP) ? ABE_BOTTOM : edge;
2750 mClearNCEdge = Some(clearEdge);
2752 } else {
2753 mNonClientOffset = NormalWindowNonClientOffset();
2756 if (aReflowWindow) {
2757 // Force a reflow of content based on the new client
2758 // dimensions.
2759 ResetLayout();
2762 return true;
2765 nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) {
2766 if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None)
2767 return NS_ERROR_INVALID_ARG;
2769 if (mHideChrome) {
2770 mFutureMarginsOnceChromeShows = margins;
2771 mFutureMarginsToUse = true;
2772 return NS_OK;
2774 mFutureMarginsToUse = false;
2776 // Request for a reset
2777 if (margins.top == -1 && margins.left == -1 && margins.right == -1 &&
2778 margins.bottom == -1) {
2779 mCustomNonClient = false;
2780 mNonClientMargins = margins;
2781 // Force a reflow of content based on the new client
2782 // dimensions.
2783 ResetLayout();
2785 int windowStatus =
2786 reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty));
2787 if (windowStatus) {
2788 ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0);
2791 return NS_OK;
2794 if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 ||
2795 margins.right < -1)
2796 return NS_ERROR_INVALID_ARG;
2798 mNonClientMargins = margins;
2799 mCustomNonClient = true;
2800 if (!UpdateNonClientMargins()) {
2801 NS_WARNING("UpdateNonClientMargins failed!");
2802 return NS_OK;
2805 return NS_OK;
2808 void nsWindow::SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) {
2809 mUseResizeMarginOverrides = true;
2810 mHorResizeMargin = aResizeMargin;
2811 mVertResizeMargin = aResizeMargin;
2812 UpdateNonClientMargins();
2815 void nsWindow::InvalidateNonClientRegion() {
2816 // +-+-----------------------+-+
2817 // | | app non-client chrome | |
2818 // | +-----------------------+ |
2819 // | | app client chrome | | }
2820 // | +-----------------------+ | }
2821 // | | app content | | } area we don't want to invalidate
2822 // | +-----------------------+ | }
2823 // | | app client chrome | | }
2824 // | +-----------------------+ |
2825 // +---------------------------+ <
2826 // ^ ^ windows non-client chrome
2827 // client area = app *
2828 RECT rect;
2829 GetWindowRect(mWnd, &rect);
2830 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2831 HRGN winRgn = CreateRectRgnIndirect(&rect);
2833 // Subtract app client chrome and app content leaving
2834 // windows non-client chrome and app non-client chrome
2835 // in winRgn.
2836 GetWindowRect(mWnd, &rect);
2837 rect.top += mCaptionHeight;
2838 rect.right -= mHorResizeMargin;
2839 rect.bottom -= mVertResizeMargin;
2840 rect.left += mHorResizeMargin;
2841 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2842 HRGN clientRgn = CreateRectRgnIndirect(&rect);
2843 CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF);
2844 DeleteObject(clientRgn);
2846 // triggers ncpaint and paint events for the two areas
2847 RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE);
2848 DeleteObject(winRgn);
2851 /**************************************************************
2853 * SECTION: nsIWidget::SetBackgroundColor
2855 * Sets the window background paint color.
2857 **************************************************************/
2859 void nsWindow::SetBackgroundColor(const nscolor& aColor) {
2860 if (mBrush) ::DeleteObject(mBrush);
2862 mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor));
2863 if (mWnd != nullptr) {
2864 ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush);
2868 /**************************************************************
2870 * SECTION: nsIWidget::SetCursor
2872 * SetCursor and related utilities for manging cursor state.
2874 **************************************************************/
2876 // Set this component cursor
2877 static HCURSOR CursorFor(nsCursor aCursor) {
2878 switch (aCursor) {
2879 case eCursor_select:
2880 return ::LoadCursor(nullptr, IDC_IBEAM);
2881 case eCursor_wait:
2882 return ::LoadCursor(nullptr, IDC_WAIT);
2883 case eCursor_hyperlink:
2884 return ::LoadCursor(nullptr, IDC_HAND);
2885 case eCursor_standard:
2886 case eCursor_context_menu: // XXX See bug 258960.
2887 return ::LoadCursor(nullptr, IDC_ARROW);
2889 case eCursor_n_resize:
2890 case eCursor_s_resize:
2891 return ::LoadCursor(nullptr, IDC_SIZENS);
2893 case eCursor_w_resize:
2894 case eCursor_e_resize:
2895 return ::LoadCursor(nullptr, IDC_SIZEWE);
2897 case eCursor_nw_resize:
2898 case eCursor_se_resize:
2899 return ::LoadCursor(nullptr, IDC_SIZENWSE);
2901 case eCursor_ne_resize:
2902 case eCursor_sw_resize:
2903 return ::LoadCursor(nullptr, IDC_SIZENESW);
2905 case eCursor_crosshair:
2906 return ::LoadCursor(nullptr, IDC_CROSS);
2908 case eCursor_move:
2909 return ::LoadCursor(nullptr, IDC_SIZEALL);
2911 case eCursor_help:
2912 return ::LoadCursor(nullptr, IDC_HELP);
2914 case eCursor_copy: // CSS3
2915 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
2917 case eCursor_alias:
2918 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
2920 case eCursor_cell:
2921 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
2922 case eCursor_grab:
2923 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
2925 case eCursor_grabbing:
2926 return ::LoadCursor(nsToolkit::mDllInstance,
2927 MAKEINTRESOURCE(IDC_GRABBING));
2929 case eCursor_spinning:
2930 return ::LoadCursor(nullptr, IDC_APPSTARTING);
2932 case eCursor_zoom_in:
2933 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
2935 case eCursor_zoom_out:
2936 return ::LoadCursor(nsToolkit::mDllInstance,
2937 MAKEINTRESOURCE(IDC_ZOOMOUT));
2939 case eCursor_not_allowed:
2940 case eCursor_no_drop:
2941 return ::LoadCursor(nullptr, IDC_NO);
2943 case eCursor_col_resize:
2944 return ::LoadCursor(nsToolkit::mDllInstance,
2945 MAKEINTRESOURCE(IDC_COLRESIZE));
2947 case eCursor_row_resize:
2948 return ::LoadCursor(nsToolkit::mDllInstance,
2949 MAKEINTRESOURCE(IDC_ROWRESIZE));
2951 case eCursor_vertical_text:
2952 return ::LoadCursor(nsToolkit::mDllInstance,
2953 MAKEINTRESOURCE(IDC_VERTICALTEXT));
2955 case eCursor_all_scroll:
2956 // XXX not 100% appropriate perhaps
2957 return ::LoadCursor(nullptr, IDC_SIZEALL);
2959 case eCursor_nesw_resize:
2960 return ::LoadCursor(nullptr, IDC_SIZENESW);
2962 case eCursor_nwse_resize:
2963 return ::LoadCursor(nullptr, IDC_SIZENWSE);
2965 case eCursor_ns_resize:
2966 return ::LoadCursor(nullptr, IDC_SIZENS);
2968 case eCursor_ew_resize:
2969 return ::LoadCursor(nullptr, IDC_SIZEWE);
2971 case eCursor_none:
2972 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
2974 default:
2975 NS_ERROR("Invalid cursor type");
2976 return nullptr;
2980 static HCURSOR CursorForImage(const nsIWidget::Cursor& aCursor,
2981 CSSToLayoutDeviceScale aScale) {
2982 if (!aCursor.IsCustom()) {
2983 return nullptr;
2986 nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
2988 // Reject cursors greater than 128 pixels in either direction, to prevent
2989 // spoofing.
2990 // XXX ideally we should rescale. Also, we could modify the API to
2991 // allow trusted content to set larger cursors.
2992 if (size.width > 128 || size.height > 128) {
2993 return nullptr;
2996 LayoutDeviceIntSize layoutSize =
2997 RoundedToInt(CSSIntSize(size.width, size.height) * aScale);
2998 LayoutDeviceIntPoint hotspot =
2999 RoundedToInt(CSSIntPoint(aCursor.mHotspotX, aCursor.mHotspotY) * aScale);
3000 HCURSOR cursor;
3001 nsresult rv = nsWindowGfx::CreateIcon(aCursor.mContainer, true, hotspot,
3002 layoutSize, &cursor);
3003 if (NS_FAILED(rv)) {
3004 return nullptr;
3007 return cursor;
3010 void nsWindow::SetCursor(const Cursor& aCursor) {
3011 static HCURSOR sCurrentHCursor = nullptr;
3012 static bool sCurrentHCursorIsCustom = false;
3014 mCursor = aCursor;
3016 if (sCurrentCursor == aCursor && sCurrentHCursor && !mUpdateCursor) {
3017 // Cursors in windows are global, so even if our mUpdateCursor flag is
3018 // false we always need to make sure the Windows cursor is up-to-date,
3019 // since stuff like native drag and drop / resizers code can mutate it
3020 // outside of this method.
3021 ::SetCursor(sCurrentHCursor);
3022 return;
3025 mUpdateCursor = false;
3027 if (sCurrentHCursorIsCustom) {
3028 ::DestroyIcon(sCurrentHCursor);
3030 sCurrentHCursor = nullptr;
3031 sCurrentHCursorIsCustom = false;
3032 sCurrentCursor = aCursor;
3034 HCURSOR cursor = CursorForImage(aCursor, GetDefaultScale());
3035 bool custom = false;
3036 if (cursor) {
3037 custom = true;
3038 } else {
3039 cursor = CursorFor(aCursor.mDefaultCursor);
3042 if (!cursor) {
3043 return;
3046 sCurrentHCursor = cursor;
3047 sCurrentHCursorIsCustom = custom;
3048 ::SetCursor(cursor);
3051 /**************************************************************
3053 * SECTION: nsIWidget::Get/SetTransparencyMode
3055 * Manage the transparency mode of the window containing this
3056 * widget. Only works for popup and dialog windows when the
3057 * Desktop Window Manager compositor is not enabled.
3059 **************************************************************/
3061 TransparencyMode nsWindow::GetTransparencyMode() {
3062 return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
3065 void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
3066 nsWindow* window = GetTopLevelWindow(true);
3067 MOZ_ASSERT(window);
3069 if (!window || window->DestroyCalled()) {
3070 return;
3073 window->SetWindowTranslucencyInner(aMode);
3076 /**************************************************************
3078 * SECTION: nsIWidget::UpdateWindowDraggingRegion
3080 * For setting the draggable titlebar region from CSS
3081 * with -moz-window-dragging: drag.
3083 **************************************************************/
3085 void nsWindow::UpdateWindowDraggingRegion(
3086 const LayoutDeviceIntRegion& aRegion) {
3087 if (mDraggableRegion != aRegion) {
3088 mDraggableRegion = aRegion;
3092 /**************************************************************
3094 * SECTION: nsIWidget::HideWindowChrome
3096 * Show or hide window chrome.
3098 **************************************************************/
3100 void nsWindow::HideWindowChrome(bool aShouldHide) {
3101 HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
3102 if (!WinUtils::GetNSWindowPtr(hwnd)) {
3103 NS_WARNING("Trying to hide window decorations in an embedded context");
3104 return;
3107 if (mHideChrome == aShouldHide) return;
3109 DWORD_PTR style, exStyle;
3110 mHideChrome = aShouldHide;
3111 if (aShouldHide) {
3112 DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
3113 DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
3115 style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME);
3116 exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
3117 WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
3119 mOldStyle = tempStyle;
3120 mOldExStyle = tempExStyle;
3121 } else {
3122 if (!mOldStyle || !mOldExStyle) {
3123 mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
3124 mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
3127 style = mOldStyle;
3128 exStyle = mOldExStyle;
3129 if (mFutureMarginsToUse) {
3130 SetNonClientMargins(mFutureMarginsOnceChromeShows);
3134 VERIFY_WINDOW_STYLE(style);
3135 ::SetWindowLongPtrW(hwnd, GWL_STYLE, style);
3136 ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle);
3139 /**************************************************************
3141 * SECTION: nsWindow::Invalidate
3143 * Invalidate an area of the client for painting.
3145 **************************************************************/
3147 // Invalidate this component visible area
3148 void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
3149 bool aIncludeChildren) {
3150 if (!mWnd) {
3151 return;
3154 #ifdef WIDGET_DEBUG_OUTPUT
3155 debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
3156 #endif // WIDGET_DEBUG_OUTPUT
3158 DWORD flags = RDW_INVALIDATE;
3159 if (aEraseBackground) {
3160 flags |= RDW_ERASE;
3162 if (aUpdateNCArea) {
3163 flags |= RDW_FRAME;
3165 if (aIncludeChildren) {
3166 flags |= RDW_ALLCHILDREN;
3169 VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
3172 // Invalidate this component visible area
3173 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
3174 if (mWnd) {
3175 #ifdef WIDGET_DEBUG_OUTPUT
3176 debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
3177 #endif // WIDGET_DEBUG_OUTPUT
3179 RECT rect;
3181 rect.left = aRect.X();
3182 rect.top = aRect.Y();
3183 rect.right = aRect.XMost();
3184 rect.bottom = aRect.YMost();
3186 VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
3190 static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
3191 WPARAM wParam,
3192 LPARAM lParam) {
3193 switch (uMsg) {
3194 case WM_FULLSCREEN_TRANSITION_BEFORE:
3195 case WM_FULLSCREEN_TRANSITION_AFTER: {
3196 DWORD duration = (DWORD)lParam;
3197 DWORD flags = AW_BLEND;
3198 if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
3199 flags |= AW_HIDE;
3201 ::AnimateWindow(hWnd, duration, flags);
3202 // The message sender should have added ref for us.
3203 NS_DispatchToMainThread(
3204 already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
3205 break;
3207 case WM_DESTROY:
3208 ::PostQuitMessage(0);
3209 break;
3210 default:
3211 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
3213 return 0;
3216 struct FullscreenTransitionInitData {
3217 LayoutDeviceIntRect mBounds;
3218 HANDLE mSemaphore;
3219 HANDLE mThread;
3220 HWND mWnd;
3222 FullscreenTransitionInitData()
3223 : mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
3225 ~FullscreenTransitionInitData() {
3226 if (mSemaphore) {
3227 ::CloseHandle(mSemaphore);
3229 if (mThread) {
3230 ::CloseHandle(mThread);
3235 static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
3236 // Initialize window class
3237 static bool sInitialized = false;
3238 if (!sInitialized) {
3239 WNDCLASSW wc = {};
3240 wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
3241 wc.hInstance = nsToolkit::mDllInstance;
3242 wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
3243 wc.lpszClassName = kClassNameTransition;
3244 ::RegisterClassW(&wc);
3245 sInitialized = true;
3248 auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
3249 HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
3250 nullptr, nsToolkit::mDllInstance, nullptr);
3251 if (!wnd) {
3252 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3253 return 0;
3256 // Since AnimateWindow blocks the thread of the transition window,
3257 // we need to hide the cursor for that window, otherwise the system
3258 // would show the busy pointer to the user.
3259 ::ShowCursor(false);
3260 ::SetWindowLongW(wnd, GWL_STYLE, 0);
3261 ::SetWindowLongW(
3262 wnd, GWL_EXSTYLE,
3263 WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
3264 ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
3265 data->mBounds.Width(), data->mBounds.Height(), 0);
3266 data->mWnd = wnd;
3267 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3268 // The initialization data may no longer be valid
3269 // after we release the semaphore.
3270 data = nullptr;
3272 MSG msg;
3273 while (::GetMessageW(&msg, nullptr, 0, 0)) {
3274 ::TranslateMessage(&msg);
3275 ::DispatchMessage(&msg);
3277 ::ShowCursor(true);
3278 ::DestroyWindow(wnd);
3279 return 0;
3282 class FullscreenTransitionData final : public nsISupports {
3283 public:
3284 NS_DECL_ISUPPORTS
3286 explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
3287 MOZ_ASSERT(NS_IsMainThread(),
3288 "FullscreenTransitionData "
3289 "should be constructed in the main thread");
3292 const HWND mWnd;
3294 private:
3295 ~FullscreenTransitionData() {
3296 MOZ_ASSERT(NS_IsMainThread(),
3297 "FullscreenTransitionData "
3298 "should be deconstructed in the main thread");
3299 ::PostMessageW(mWnd, WM_DESTROY, 0, 0);
3303 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
3305 /* virtual */
3306 bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
3307 FullscreenTransitionInitData initData;
3308 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
3309 const DesktopIntRect rect = screen->GetRectDisplayPix();
3310 MOZ_ASSERT(BoundsUseDesktopPixels(),
3311 "Should only be called on top-level window");
3312 initData.mBounds =
3313 LayoutDeviceIntRect::Round(rect * GetDesktopToDeviceScale());
3315 // Create a semaphore for synchronizing the window handle which will
3316 // be created by the transition thread and used by the main thread for
3317 // posting the transition messages.
3318 initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
3319 if (initData.mSemaphore) {
3320 initData.mThread = ::CreateThread(
3321 nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
3322 if (initData.mThread) {
3323 ::WaitForSingleObject(initData.mSemaphore, INFINITE);
3326 if (!initData.mWnd) {
3327 return false;
3330 mTransitionWnd = initData.mWnd;
3332 auto data = new FullscreenTransitionData(initData.mWnd);
3333 *aData = data;
3334 NS_ADDREF(data);
3335 return true;
3338 /* virtual */
3339 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
3340 uint16_t aDuration,
3341 nsISupports* aData,
3342 nsIRunnable* aCallback) {
3343 auto data = static_cast<FullscreenTransitionData*>(aData);
3344 nsCOMPtr<nsIRunnable> callback = aCallback;
3345 UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
3346 : WM_FULLSCREEN_TRANSITION_AFTER;
3347 WPARAM wparam = (WPARAM)callback.forget().take();
3348 ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
3351 /* virtual */
3352 void nsWindow::CleanupFullscreenTransition() {
3353 MOZ_ASSERT(NS_IsMainThread(),
3354 "CleanupFullscreenTransition "
3355 "should only run on the main thread");
3357 mTransitionWnd = nullptr;
3360 void nsWindow::TryDwmResizeHack() {
3361 // The "DWM resize hack", aka the "fullscreen resize hack", is a workaround
3362 // for DWM's occasional and not-entirely-predictable failure to update its
3363 // internal state when the client area of a window changes without changing
3364 // the window size. The effect of this is that DWM will clip the content of
3365 // the window to its former client area.
3367 // It is not known under what circumstances the bug will trigger. Windows 11
3368 // is known to be required, but many Windows 11 machines do not exhibit the
3369 // issue. Even machines that _do_ exhibit it will sometimes not do so when
3370 // apparently-irrelevant changes are made to the configuration. (See bug
3371 // 1763981.)
3373 // The bug is triggered by Firefox when a maximized window (which has window
3374 // decorations) becomes fullscreen (which doesn't). To work around this, if we
3375 // think it may occur, we "flicker-resize" the relevant window -- that is, we
3376 // reduce its height by 1px, then restore it. This causes DWM to acquire the
3377 // new client-area metrics.
3379 // Note that, in particular, this bug will not occur when using a separate
3380 // compositor window, as our compositor windows never have any nonclient area.
3382 // This is admittedly a sledgehammer where a screwdriver should suffice.
3384 // ---------------------------------------------------------------------------
3386 // Regardless of preferences or heuristics, only apply the hack if this is the
3387 // first time we've entered fullscreen across the entire Firefox session.
3388 // (Subsequent transitions to fullscreen, even with different windows, don't
3389 // appear to induce the bug.)
3391 // (main thread only; `atomic` not needed)
3392 static bool sIsFirstFullscreenEntry = true;
3393 bool isFirstFullscreenEntry = sIsFirstFullscreenEntry;
3394 sIsFirstFullscreenEntry = false;
3395 if (MOZ_LIKELY(!isFirstFullscreenEntry)) {
3396 return;
3398 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3399 ("%s: first fullscreen entry", __PRETTY_FUNCTION__));
3402 // Check whether to try to apply the DWM resize hack, based on the override
3403 // pref and/or some internal heuristics.
3405 const auto hackApplicationHeuristics = [&]() -> bool {
3406 // The bug has only been seen under Windows 11. (At time of writing, this
3407 // is the latest version of Windows.)
3408 if (!IsWin11OrLater()) {
3409 return false;
3412 KnowsCompositor const* const kc = mWindowRenderer->AsKnowsCompositor();
3413 // This should never happen...
3414 MOZ_ASSERT(kc);
3415 // ... so if it does, we are in uncharted territory: don't apply the hack.
3416 if (!kc) {
3417 return false;
3420 // The bug doesn't occur when we're using a separate compositor window
3421 // (since the compositor window always comprises exactly its client area,
3422 // with no non-client border).
3423 if (kc->GetUseCompositorWnd()) {
3424 return false;
3427 // Otherwise, apply the hack.
3428 return true;
3431 // Figure out whether or not we should perform the hack, and -- arguably
3432 // more importantly -- log that decision.
3433 bool const shouldApplyHack = [&]() {
3434 enum Reason : bool { Pref, Heuristics };
3435 auto const msg = [&](bool decision, Reason reason) -> bool {
3436 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3437 ("%s %s per %s", decision ? "applying" : "skipping",
3438 "DWM resize hack", reason == Pref ? "pref" : "heuristics"));
3439 return decision;
3441 switch (StaticPrefs::widget_windows_apply_dwm_resize_hack()) {
3442 case 0:
3443 return msg(false, Pref);
3444 case 1:
3445 return msg(true, Pref);
3446 default: // treat all other values as `auto`
3447 return msg(hackApplicationHeuristics(), Heuristics);
3449 }();
3451 if (!shouldApplyHack) {
3452 return;
3456 // The DWM bug is believed to involve a race condition: some users have
3457 // reported that setting a custom theme or adding unused command-line
3458 // parameters sometimes causes the bug to vanish.
3460 // Out of an abundance of caution, we therefore apply the hack in a later
3461 // event, rather than inline.
3462 NS_DispatchToMainThread(NS_NewRunnableFunction(
3463 "nsWindow::TryFullscreenResizeHack", [self = RefPtr(this)]() {
3464 HWND const hwnd = self->GetWindowHandle();
3466 if (self->mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
3467 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info,
3468 ("DWM resize hack: window no longer fullscreen; aborting"));
3469 return;
3472 RECT origRect;
3473 if (!::GetWindowRect(hwnd, &origRect)) {
3474 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Error,
3475 ("DWM resize hack: could not get window size?!"));
3476 return;
3478 LONG const x = origRect.left;
3479 LONG const y = origRect.top;
3480 LONG const width = origRect.right - origRect.left;
3481 LONG const height = origRect.bottom - origRect.top;
3483 MOZ_DIAGNOSTIC_ASSERT(!self->mIsPerformingDwmFlushHack);
3484 auto const onExit =
3485 MakeScopeExit([&, oldVal = self->mIsPerformingDwmFlushHack]() {
3486 self->mIsPerformingDwmFlushHack = oldVal;
3488 self->mIsPerformingDwmFlushHack = true;
3490 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3491 ("beginning DWM resize hack for HWND %08" PRIXPTR,
3492 uintptr_t(hwnd)));
3493 ::MoveWindow(hwnd, x, y, width, height - 1, FALSE);
3494 ::MoveWindow(hwnd, x, y, width, height, TRUE);
3495 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3496 ("concluded DWM resize hack for HWND %08" PRIXPTR,
3497 uintptr_t(hwnd)));
3498 }));
3501 void nsWindow::OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen) {
3502 MOZ_ASSERT((aOldSizeMode != nsSizeMode_Fullscreen) == aFullScreen);
3504 // HACK: Potentially flicker-resize the window, to force DWM to get the right
3505 // client-area information.
3506 if (aFullScreen) {
3507 TryDwmResizeHack();
3510 // Hide chrome and reposition window. Note this will also cache dimensions for
3511 // restoration, so it should only be called once per fullscreen request.
3513 // Don't do this when minimized, since our bounds make no sense then, nor when
3514 // coming back from that state.
3515 const bool toOrFromMinimized =
3516 mFrameState->GetSizeMode() == nsSizeMode_Minimized ||
3517 aOldSizeMode == nsSizeMode_Minimized;
3518 if (!toOrFromMinimized) {
3519 InfallibleMakeFullScreen(aFullScreen);
3522 // Possibly notify the taskbar that we have changed our fullscreen mode.
3523 TaskbarConcealer::OnFullscreenChanged(this, aFullScreen);
3526 nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
3527 mFrameState->EnsureFullscreenMode(aFullScreen);
3528 return NS_OK;
3531 /**************************************************************
3533 * SECTION: Native data storage
3535 * nsIWidget::GetNativeData
3536 * nsIWidget::FreeNativeData
3538 * Set or clear native data based on a constant.
3540 **************************************************************/
3542 // Return some native data according to aDataType
3543 void* nsWindow::GetNativeData(uint32_t aDataType) {
3544 switch (aDataType) {
3545 case NS_NATIVE_WIDGET:
3546 case NS_NATIVE_WINDOW:
3547 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
3548 return (void*)mWnd;
3549 case NS_NATIVE_GRAPHIC:
3550 MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
3551 return nullptr;
3552 case NS_RAW_NATIVE_IME_CONTEXT: {
3553 void* pseudoIMEContext = GetPseudoIMEContext();
3554 if (pseudoIMEContext) {
3555 return pseudoIMEContext;
3557 [[fallthrough]];
3559 case NS_NATIVE_TSF_THREAD_MGR:
3560 case NS_NATIVE_TSF_CATEGORY_MGR:
3561 case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
3562 return IMEHandler::GetNativeData(this, aDataType);
3564 default:
3565 break;
3568 return nullptr;
3571 // Free some native data according to aDataType
3572 void nsWindow::FreeNativeData(void* data, uint32_t aDataType) {
3573 switch (aDataType) {
3574 case NS_NATIVE_GRAPHIC:
3575 case NS_NATIVE_WIDGET:
3576 case NS_NATIVE_WINDOW:
3577 break;
3578 default:
3579 break;
3583 /**************************************************************
3585 * SECTION: nsIWidget::SetTitle
3587 * Set the main windows title text.
3589 **************************************************************/
3591 nsresult nsWindow::SetTitle(const nsAString& aTitle) {
3592 const nsString& strTitle = PromiseFlatString(aTitle);
3593 AutoRestore<bool> sendingText(mSendingSetText);
3594 mSendingSetText = true;
3595 ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
3596 return NS_OK;
3599 /**************************************************************
3601 * SECTION: nsIWidget::SetIcon
3603 * Set the main windows icon.
3605 **************************************************************/
3607 void nsWindow::SetBigIcon(HICON aIcon) {
3608 HICON icon =
3609 (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
3610 if (icon) {
3611 ::DestroyIcon(icon);
3614 mIconBig = aIcon;
3617 void nsWindow::SetSmallIcon(HICON aIcon) {
3618 HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
3619 (LPARAM)aIcon);
3620 if (icon) {
3621 ::DestroyIcon(icon);
3624 mIconSmall = aIcon;
3627 void nsWindow::SetIcon(const nsAString& aIconSpec) {
3628 // Assume the given string is a local identifier for an icon file.
3630 nsCOMPtr<nsIFile> iconFile;
3631 ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
3632 if (!iconFile) return;
3634 nsAutoString iconPath;
3635 iconFile->GetPath(iconPath);
3637 // XXX this should use MZLU (see bug 239279)
3639 ::SetLastError(0);
3641 HICON bigIcon =
3642 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3643 ::GetSystemMetrics(SM_CXICON),
3644 ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
3645 HICON smallIcon =
3646 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3647 ::GetSystemMetrics(SM_CXSMICON),
3648 ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
3650 if (bigIcon) {
3651 SetBigIcon(bigIcon);
3653 #ifdef DEBUG_SetIcon
3654 else {
3655 NS_LossyConvertUTF16toASCII cPath(iconPath);
3656 MOZ_LOG(gWindowsLog, LogLevel::Info,
3657 ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3658 ::GetLastError()));
3660 #endif
3661 if (smallIcon) {
3662 SetSmallIcon(smallIcon);
3664 #ifdef DEBUG_SetIcon
3665 else {
3666 NS_LossyConvertUTF16toASCII cPath(iconPath);
3667 MOZ_LOG(gWindowsLog, LogLevel::Info,
3668 ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3669 ::GetLastError()));
3671 #endif
3674 void nsWindow::SetBigIconNoData() {
3675 HICON bigIcon =
3676 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3677 SetBigIcon(bigIcon);
3680 void nsWindow::SetSmallIconNoData() {
3681 HICON smallIcon =
3682 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3683 SetSmallIcon(smallIcon);
3686 /**************************************************************
3688 * SECTION: nsIWidget::WidgetToScreenOffset
3690 * Return this widget's origin in screen coordinates.
3692 **************************************************************/
3694 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
3695 POINT point;
3696 point.x = 0;
3697 point.y = 0;
3698 ::ClientToScreen(mWnd, &point);
3699 return LayoutDeviceIntPoint(point.x, point.y);
3702 LayoutDeviceIntMargin nsWindow::ClientToWindowMargin() {
3703 if (mWindowType == WindowType::Popup && !IsPopupWithTitleBar()) {
3704 return {};
3707 if (mCustomNonClient) {
3708 return NonClientSizeMargin(NormalWindowNonClientOffset());
3711 // Just use a dummy 200x200 at (200, 200) client rect as the rect.
3712 RECT clientRect;
3713 clientRect.left = 200;
3714 clientRect.top = 200;
3715 clientRect.right = 400;
3716 clientRect.bottom = 400;
3718 auto ToRect = [](const RECT& aRect) -> LayoutDeviceIntRect {
3719 return {aRect.left, aRect.top, aRect.right - aRect.left,
3720 aRect.bottom - aRect.top};
3723 RECT windowRect = clientRect;
3724 ::AdjustWindowRectEx(&windowRect, WindowStyle(), false, WindowExStyle());
3726 return ToRect(windowRect) - ToRect(clientRect);
3729 /**************************************************************
3731 * SECTION: nsIWidget::EnableDragDrop
3733 * Enables/Disables drag and drop of files on this widget.
3735 **************************************************************/
3737 void nsWindow::EnableDragDrop(bool aEnable) {
3738 if (!mWnd) {
3739 // Return early if the window already closed
3740 return;
3743 if (aEnable) {
3744 if (!mNativeDragTarget) {
3745 mNativeDragTarget = new nsNativeDragTarget(this);
3746 mNativeDragTarget->AddRef();
3747 ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
3749 } else {
3750 if (mWnd && mNativeDragTarget) {
3751 ::RevokeDragDrop(mWnd);
3752 mNativeDragTarget->DragCancel();
3753 NS_RELEASE(mNativeDragTarget);
3758 /**************************************************************
3760 * SECTION: nsIWidget::CaptureMouse
3762 * Enables/Disables system mouse capture.
3764 **************************************************************/
3766 void nsWindow::CaptureMouse(bool aCapture) {
3767 TRACKMOUSEEVENT mTrack;
3768 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
3769 mTrack.dwHoverTime = 0;
3770 mTrack.hwndTrack = mWnd;
3771 if (aCapture) {
3772 mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
3773 ::SetCapture(mWnd);
3774 } else {
3775 mTrack.dwFlags = TME_LEAVE;
3776 ::ReleaseCapture();
3778 sIsInMouseCapture = aCapture;
3779 TrackMouseEvent(&mTrack);
3782 /**************************************************************
3784 * SECTION: nsIWidget::CaptureRollupEvents
3786 * Dealing with event rollup on destroy for popups. Enables &
3787 * Disables system capture of any and all events that would
3788 * cause a dropdown to be rolled up.
3790 **************************************************************/
3792 void nsWindow::CaptureRollupEvents(bool aDoCapture) {
3793 if (aDoCapture) {
3794 if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
3795 RegisterSpecialDropdownHooks();
3797 sProcessHook = true;
3798 } else {
3799 sProcessHook = false;
3800 UnregisterSpecialDropdownHooks();
3804 /**************************************************************
3806 * SECTION: nsIWidget::GetAttention
3808 * Bring this window to the user's attention.
3810 **************************************************************/
3812 // Draw user's attention to this window until it comes to foreground.
3813 nsresult nsWindow::GetAttention(int32_t aCycleCount) {
3814 // Got window?
3815 if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
3817 HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
3818 HWND fgWnd = ::GetForegroundWindow();
3819 // Don't flash if the flash count is 0 or if the foreground window is our
3820 // window handle or that of our owned-most window.
3821 if (aCycleCount == 0 || flashWnd == fgWnd ||
3822 flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
3823 return NS_OK;
3826 DWORD defaultCycleCount = 0;
3827 ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
3829 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
3830 aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
3831 ::FlashWindowEx(&flashInfo);
3833 return NS_OK;
3836 void nsWindow::StopFlashing() {
3837 HWND flashWnd = mWnd;
3838 while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
3839 flashWnd = ownerWnd;
3842 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
3843 ::FlashWindowEx(&flashInfo);
3846 /**************************************************************
3848 * SECTION: nsIWidget::HasPendingInputEvent
3850 * Ask whether there user input events pending. All input events are
3851 * included, including those not targeted at this nsIwidget instance.
3853 **************************************************************/
3855 bool nsWindow::HasPendingInputEvent() {
3856 // If there is pending input or the user is currently
3857 // moving the window then return true.
3858 // Note: When the user is moving the window WIN32 spins
3859 // a separate event loop and input events are not
3860 // reported to the application.
3861 if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
3862 GUITHREADINFO guiInfo;
3863 guiInfo.cbSize = sizeof(GUITHREADINFO);
3864 if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
3865 return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
3868 /**************************************************************
3870 * SECTION: nsIWidget::GetWindowRenderer
3872 * Get the window renderer associated with this widget.
3874 **************************************************************/
3876 WindowRenderer* nsWindow::GetWindowRenderer() {
3877 if (mWindowRenderer) {
3878 return mWindowRenderer;
3881 if (!mLocalesChangedObserver) {
3882 mLocalesChangedObserver = new LocalesChangedObserver(this);
3885 // Try OMTC first.
3886 if (!mWindowRenderer && ShouldUseOffMainThreadCompositing()) {
3887 gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
3888 CreateCompositor();
3891 if (!mWindowRenderer) {
3892 MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
3893 MOZ_ASSERT(!mCompositorWidgetDelegate);
3895 // Ensure we have a widget proxy even if we're not using the compositor,
3896 // since all our transparent window handling lives there.
3897 WinCompositorWidgetInitData initData(
3898 reinterpret_cast<uintptr_t>(mWnd),
3899 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
3900 mTransparencyMode, mFrameState->GetSizeMode());
3901 // If we're not using the compositor, the options don't actually matter.
3902 CompositorOptions options(false, false);
3903 mBasicLayersSurface =
3904 new InProcessWinCompositorWidget(initData, options, this);
3905 mCompositorWidgetDelegate = mBasicLayersSurface;
3906 mWindowRenderer = CreateFallbackRenderer();
3909 NS_ASSERTION(mWindowRenderer, "Couldn't provide a valid window renderer.");
3911 if (mWindowRenderer) {
3912 // Update the size constraints now that the layer manager has been
3913 // created.
3914 KnowsCompositor* knowsCompositor = mWindowRenderer->AsKnowsCompositor();
3915 if (knowsCompositor) {
3916 SizeConstraints c = mSizeConstraints;
3917 mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
3918 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
3919 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
3920 nsBaseWidget::SetSizeConstraints(c);
3924 return mWindowRenderer;
3927 /**************************************************************
3929 * SECTION: nsBaseWidget::SetCompositorWidgetDelegate
3931 * Called to connect the nsWindow to the delegate providing
3932 * platform compositing API access.
3934 **************************************************************/
3936 void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
3937 if (delegate) {
3938 mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
3939 MOZ_ASSERT(mCompositorWidgetDelegate,
3940 "nsWindow::SetCompositorWidgetDelegate called with a "
3941 "non-PlatformCompositorWidgetDelegate");
3942 } else {
3943 mCompositorWidgetDelegate = nullptr;
3947 /**************************************************************
3949 * SECTION: nsIWidget::OnDefaultButtonLoaded
3951 * Called after the dialog is loaded and it has a default button.
3953 **************************************************************/
3955 nsresult nsWindow::OnDefaultButtonLoaded(
3956 const LayoutDeviceIntRect& aButtonRect) {
3957 if (aButtonRect.IsEmpty()) return NS_OK;
3959 // Don't snap when we are not active.
3960 HWND activeWnd = ::GetActiveWindow();
3961 if (activeWnd != ::GetForegroundWindow() ||
3962 WinUtils::GetTopLevelHWND(mWnd, true) !=
3963 WinUtils::GetTopLevelHWND(activeWnd, true)) {
3964 return NS_OK;
3967 bool isAlwaysSnapCursor =
3968 Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
3970 if (!isAlwaysSnapCursor) {
3971 BOOL snapDefaultButton;
3972 if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
3973 0) ||
3974 !snapDefaultButton)
3975 return NS_OK;
3978 LayoutDeviceIntRect widgetRect = GetScreenBounds();
3979 LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
3981 LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
3982 buttonRect.Y() + buttonRect.Height() / 2);
3983 // The center of the button can be outside of the widget.
3984 // E.g., it could be hidden by scrolling.
3985 if (!widgetRect.Contains(centerOfButton)) {
3986 return NS_OK;
3989 if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
3990 NS_ERROR("SetCursorPos failed");
3991 return NS_ERROR_FAILURE;
3993 return NS_OK;
3996 uint32_t nsWindow::GetMaxTouchPoints() const {
3997 return WinUtils::GetMaxTouchPoints();
4000 void nsWindow::SetWindowClass(const nsAString& xulWinType,
4001 const nsAString& xulWinClass,
4002 const nsAString& xulWinName) {
4003 mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank");
4006 /**************************************************************
4007 **************************************************************
4009 ** BLOCK: Moz Events
4011 ** Moz GUI event management.
4013 **************************************************************
4014 **************************************************************/
4016 /**************************************************************
4018 * SECTION: Mozilla event initialization
4020 * Helpers for initializing moz events.
4022 **************************************************************/
4024 // Event initialization
4025 void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
4026 if (nullptr == aPoint) { // use the point from the event
4027 // get the message position in client coordinates
4028 if (mWnd != nullptr) {
4029 DWORD pos = ::GetMessagePos();
4030 POINT cpos;
4032 cpos.x = GET_X_LPARAM(pos);
4033 cpos.y = GET_Y_LPARAM(pos);
4035 ::ScreenToClient(mWnd, &cpos);
4036 event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
4037 } else {
4038 event.mRefPoint = LayoutDeviceIntPoint(0, 0);
4040 } else {
4041 // use the point override if provided
4042 event.mRefPoint = *aPoint;
4045 event.AssignEventTime(CurrentMessageWidgetEventTime());
4048 WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
4049 LONG messageTime = ::GetMessageTime();
4050 return WidgetEventTime(GetMessageTimeStamp(messageTime));
4053 /**************************************************************
4055 * SECTION: Moz event dispatch helpers
4057 * Helpers for dispatching different types of moz events.
4059 **************************************************************/
4061 // Main event dispatch. Invokes callback and ProcessEvent method on
4062 // Event Listener object. Part of nsIWidget.
4063 nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
4064 nsEventStatus& aStatus) {
4065 #ifdef WIDGET_DEBUG_OUTPUT
4066 debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
4067 #endif // WIDGET_DEBUG_OUTPUT
4069 aStatus = nsEventStatus_eIgnore;
4071 // Top level windows can have a view attached which requires events be sent
4072 // to the underlying base window and the view. Added when we combined the
4073 // base chrome window with the main content child for nc client area (title
4074 // bar) rendering.
4075 if (mAttachedWidgetListener) {
4076 aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
4077 } else if (mWidgetListener) {
4078 aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
4081 // the window can be destroyed during processing of seemingly innocuous events
4082 // like, say, mousedowns due to the magic of scripting. mousedowns will return
4083 // nsEventStatus_eIgnore, which causes problems with the deleted window.
4084 // therefore:
4085 if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
4086 return NS_OK;
4089 bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
4090 WidgetGUIEvent event(true, aMsg, this);
4091 InitEvent(event);
4093 bool result = DispatchWindowEvent(event);
4094 return result;
4097 bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
4098 nsEventStatus status = DispatchInputEvent(event).mContentStatus;
4099 return ConvertStatus(status);
4102 bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
4103 nsEventStatus status;
4104 DispatchEvent(aEvent, status);
4105 return ConvertStatus(status);
4108 bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
4109 nsEventStatus status =
4110 DispatchInputEvent(aEvent->AsInputEvent()).mContentStatus;
4111 return ConvertStatus(status);
4114 // Recursively dispatch synchronous paints for nsIWidget
4115 // descendants with invalidated rectangles.
4116 BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
4117 LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
4118 if (proc == (LONG_PTR)&nsWindow::WindowProc) {
4119 // its one of our windows so check to see if it has a
4120 // invalidated rect. If it does. Dispatch a synchronous
4121 // paint.
4122 if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
4124 return TRUE;
4127 // Check for pending paints and dispatch any pending paint
4128 // messages for any nsIWidget which is a descendant of the
4129 // top-level window that *this* window is embedded within.
4131 // Note: We do not dispatch pending paint messages for non
4132 // nsIWidget managed windows.
4133 void nsWindow::DispatchPendingEvents() {
4134 // We need to ensure that reflow events do not get starved.
4135 // At the same time, we don't want to recurse through here
4136 // as that would prevent us from dispatching starved paints.
4137 static int recursionBlocker = 0;
4138 if (recursionBlocker++ == 0) {
4139 NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
4140 --recursionBlocker;
4143 // Quickly check to see if there are any paint events pending,
4144 // but only dispatch them if it has been long enough since the
4145 // last paint completed.
4146 if (::GetQueueStatus(QS_PAINT) &&
4147 ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
4148 // Find the top level window.
4149 HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
4151 // Dispatch pending paints for topWnd and all its descendant windows.
4152 // Note: EnumChildWindows enumerates all descendant windows not just
4153 // the children (but not the window itself).
4154 nsWindow::DispatchStarvedPaints(topWnd, 0);
4155 ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
4159 void nsWindow::DispatchCustomEvent(const nsString& eventName) {
4160 if (Document* doc = GetDocument()) {
4161 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
4162 win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
4167 bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
4168 LayoutDeviceIntPoint aEventPoint) {
4169 // Allow users to start dragging by double-tapping.
4170 if (aEventMessage == eMouseDoubleClick) {
4171 return true;
4174 // In chrome UI, allow touchdownstartsdrag attributes
4175 // to cause any touchdown event to trigger a drag.
4176 if (aEventMessage == eMouseDown) {
4177 WidgetMouseEvent hittest(true, eMouseHitTest, this,
4178 WidgetMouseEvent::eReal);
4179 hittest.mRefPoint = aEventPoint;
4180 hittest.mIgnoreRootScrollFrame = true;
4181 hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
4182 DispatchInputEvent(&hittest);
4184 if (EventTarget* target = hittest.GetDOMEventTarget()) {
4185 if (nsIContent* content = nsIContent::FromEventTarget(target)) {
4186 // Check if the element or any parent element has the
4187 // attribute we're looking for.
4188 for (Element* element = content->GetAsElementOrParentElement(); element;
4189 element = element->GetParentElement()) {
4190 nsAutoString startDrag;
4191 element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
4192 if (!startDrag.IsEmpty()) {
4193 return true;
4200 return false;
4203 // Deal with all sort of mouse event
4204 bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
4205 LPARAM lParam, bool aIsContextMenuKey,
4206 int16_t aButton, uint16_t aInputSource,
4207 WinPointerInfo* aPointerInfo,
4208 bool aIgnoreAPZ) {
4209 ContextMenuPreventer contextMenuPreventer(this);
4210 bool result = false;
4212 UserActivity();
4214 if (!mWidgetListener) {
4215 return result;
4218 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
4219 LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
4221 // Suppress mouse moves caused by widget creation. Make sure to do this early
4222 // so that we update sLastMouseMovePoint even for touch-induced mousemove
4223 // events.
4224 if (aEventMessage == eMouseMove) {
4225 if ((sLastMouseMovePoint.x == mpScreen.x.value) &&
4226 (sLastMouseMovePoint.y == mpScreen.y.value)) {
4227 return result;
4229 sLastMouseMovePoint.x = mpScreen.x;
4230 sLastMouseMovePoint.y = mpScreen.y;
4233 if (!aIgnoreAPZ && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
4234 if (mTouchWindow) {
4235 // If mTouchWindow is true, then we must have APZ enabled and be
4236 // feeding it raw touch events. In that case we only want to
4237 // send touch-generated mouse events to content if they should
4238 // start a touch-based drag-and-drop gesture, such as on
4239 // double-tapping or when tapping elements marked with the
4240 // touchdownstartsdrag attribute in chrome UI.
4241 MOZ_ASSERT(mAPZC);
4242 if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
4243 aEventMessage = eMouseTouchDrag;
4244 } else {
4245 return result;
4250 uint32_t pointerId =
4251 aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
4253 switch (aEventMessage) {
4254 case eMouseDown:
4255 CaptureMouse(true);
4256 break;
4258 // eMouseMove and eMouseExitFromWidget are here because we need to make
4259 // sure capture flag isn't left on after a drag where we wouldn't see a
4260 // button up message (see bug 324131).
4261 case eMouseUp:
4262 case eMouseMove:
4263 case eMouseExitFromWidget:
4264 if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
4265 sIsInMouseCapture)
4266 CaptureMouse(false);
4267 break;
4269 default:
4270 break;
4272 } // switch
4274 WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal,
4275 aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
4276 : WidgetMouseEvent::eNormal);
4277 if (aEventMessage == eContextMenu && aIsContextMenuKey) {
4278 LayoutDeviceIntPoint zero(0, 0);
4279 InitEvent(event, &zero);
4280 } else {
4281 InitEvent(event, &eventPoint);
4284 ModifierKeyState modifierKeyState;
4285 modifierKeyState.InitInputEvent(event);
4287 // eContextMenu with Shift state is special. It won't fire "contextmenu"
4288 // event in the web content for blocking web content to prevent its default.
4289 // However, Shift+F10 is a standard shortcut key on Windows. Therefore,
4290 // this should not block web page to prevent its default. I.e., it should
4291 // behave same as ContextMenu key without Shift key.
4292 // XXX Should we allow to block web page to prevent its default with
4293 // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
4294 if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() &&
4295 NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
4296 NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
4297 event.mModifiers &= ~MODIFIER_SHIFT;
4300 event.mButton = aButton;
4301 event.mInputSource = aInputSource;
4302 if (aPointerInfo) {
4303 // Mouse events from Windows WM_POINTER*. Fill more information in
4304 // WidgetMouseEvent.
4305 event.AssignPointerHelperData(*aPointerInfo);
4306 event.mPressure = aPointerInfo->mPressure;
4307 event.mButtons = aPointerInfo->mButtons;
4308 } else {
4309 // If we get here the mouse events must be from non-touch sources, so
4310 // convert it to pointer events as well
4311 event.convertToPointer = true;
4312 event.pointerId = pointerId;
4315 // Static variables used to distinguish simple-, double- and triple-clicks.
4316 static POINT sLastMousePoint = {0};
4317 static LONG sLastMouseDownTime = 0L;
4318 static LONG sLastClickCount = 0L;
4319 static BYTE sLastMouseButton = 0;
4321 bool insideMovementThreshold =
4322 (DeprecatedAbs(sLastMousePoint.x - eventPoint.x.value) <
4323 (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
4324 (DeprecatedAbs(sLastMousePoint.y - eventPoint.y.value) <
4325 (short)::GetSystemMetrics(SM_CYDOUBLECLK));
4327 BYTE eventButton;
4328 switch (aButton) {
4329 case MouseButton::ePrimary:
4330 eventButton = VK_LBUTTON;
4331 break;
4332 case MouseButton::eMiddle:
4333 eventButton = VK_MBUTTON;
4334 break;
4335 case MouseButton::eSecondary:
4336 eventButton = VK_RBUTTON;
4337 break;
4338 default:
4339 eventButton = 0;
4340 break;
4343 // Doubleclicks are used to set the click count, then changed to mousedowns
4344 // We're going to time double-clicks from mouse *up* to next mouse *down*
4345 LONG curMsgTime = ::GetMessageTime();
4347 switch (aEventMessage) {
4348 case eMouseDoubleClick:
4349 event.mMessage = eMouseDown;
4350 event.mButton = aButton;
4351 sLastClickCount = 2;
4352 sLastMouseDownTime = curMsgTime;
4353 break;
4354 case eMouseUp:
4355 // remember when this happened for the next mouse down
4356 sLastMousePoint.x = eventPoint.x;
4357 sLastMousePoint.y = eventPoint.y;
4358 sLastMouseButton = eventButton;
4359 break;
4360 case eMouseDown:
4361 // now look to see if we want to convert this to a double- or triple-click
4362 if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
4363 insideMovementThreshold && eventButton == sLastMouseButton) {
4364 sLastClickCount++;
4365 } else {
4366 // reset the click count, to count *this* click
4367 sLastClickCount = 1;
4369 // Set last Click time on MouseDown only
4370 sLastMouseDownTime = curMsgTime;
4371 break;
4372 case eMouseMove:
4373 if (!insideMovementThreshold) {
4374 sLastClickCount = 0;
4376 break;
4377 case eMouseExitFromWidget:
4378 event.mExitFrom =
4379 Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
4380 : WidgetMouseEvent::ePlatformChild);
4381 break;
4382 default:
4383 break;
4385 event.mClickCount = sLastClickCount;
4387 #ifdef NS_DEBUG_XX
4388 MOZ_LOG(gWindowsLog, LogLevel::Info,
4389 ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount));
4390 #endif
4392 // call the event callback
4393 if (mWidgetListener) {
4394 if (aEventMessage == eMouseMove) {
4395 LayoutDeviceIntRect rect = GetBounds();
4396 rect.MoveTo(0, 0);
4398 if (rect.Contains(event.mRefPoint)) {
4399 if (sCurrentWindow == nullptr || sCurrentWindow != this) {
4400 if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
4401 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4402 sCurrentWindow->DispatchMouseEvent(
4403 eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
4404 aInputSource, aPointerInfo);
4406 sCurrentWindow = this;
4407 if (!mInDtor) {
4408 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4409 sCurrentWindow->DispatchMouseEvent(
4410 eMouseEnterIntoWidget, wParam, pos, false,
4411 MouseButton::ePrimary, aInputSource, aPointerInfo);
4415 } else if (aEventMessage == eMouseExitFromWidget) {
4416 if (sCurrentWindow == this) {
4417 sCurrentWindow = nullptr;
4421 nsIWidget::ContentAndAPZEventStatus eventStatus =
4422 DispatchInputEvent(&event);
4423 contextMenuPreventer.Update(event, eventStatus);
4424 return ConvertStatus(eventStatus.mContentStatus);
4427 return result;
4430 HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
4431 // retrieve the toplevel window or dialogue
4432 HWND toplevelWnd = nullptr;
4433 while (aCurWnd) {
4434 toplevelWnd = aCurWnd;
4435 nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
4436 if (win) {
4437 if (win->mWindowType == WindowType::TopLevel ||
4438 win->mWindowType == WindowType::Dialog) {
4439 break;
4443 aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
4445 return toplevelWnd;
4448 void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
4449 if (aIsActivate) {
4450 sJustGotActivate = false;
4452 sJustGotDeactivate = false;
4453 mLastKillFocusWindow = nullptr;
4455 HWND toplevelWnd = GetTopLevelForFocus(mWnd);
4457 if (toplevelWnd) {
4458 nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
4459 if (win && win->mWidgetListener) {
4460 if (aIsActivate) {
4461 win->mWidgetListener->WindowActivated();
4462 } else {
4463 win->mWidgetListener->WindowDeactivated();
4469 HWND nsWindow::WindowAtMouse() {
4470 DWORD pos = ::GetMessagePos();
4471 POINT mp;
4472 mp.x = GET_X_LPARAM(pos);
4473 mp.y = GET_Y_LPARAM(pos);
4474 return ::WindowFromPoint(mp);
4477 bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
4478 HWND mouseWnd = WindowAtMouse();
4480 // WinUtils::GetTopLevelHWND() will return a HWND for the window frame
4481 // (which includes the non-client area). If the mouse has moved into
4482 // the non-client area, we should treat it as a top-level exit.
4483 HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
4484 if (mouseWnd == mouseTopLevel) return true;
4486 return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
4489 /**************************************************************
4491 * SECTION: IPC
4493 * IPC related helpers.
4495 **************************************************************/
4497 // static
4498 bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
4499 switch (aMsg) {
4500 case WM_SETFOCUS:
4501 case WM_KILLFOCUS:
4502 case WM_ENABLE:
4503 case WM_WINDOWPOSCHANGING:
4504 case WM_WINDOWPOSCHANGED:
4505 case WM_PARENTNOTIFY:
4506 case WM_ACTIVATEAPP:
4507 case WM_NCACTIVATE:
4508 case WM_ACTIVATE:
4509 case WM_CHILDACTIVATE:
4510 case WM_IME_SETCONTEXT:
4511 case WM_IME_NOTIFY:
4512 case WM_SHOWWINDOW:
4513 case WM_CANCELMODE:
4514 case WM_MOUSEACTIVATE:
4515 case WM_CONTEXTMENU:
4516 aResult = 0;
4517 return true;
4519 case WM_SETTINGCHANGE:
4520 case WM_SETCURSOR:
4521 return false;
4524 #ifdef DEBUG
4525 char szBuf[200];
4526 sprintf(szBuf,
4527 "An unhandled ISMEX_SEND message was received during spin loop! (%X)",
4528 aMsg);
4529 NS_WARNING(szBuf);
4530 #endif
4532 return false;
4535 void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
4536 MOZ_ASSERT_IF(
4537 msg != WM_GETOBJECT,
4538 !mozilla::ipc::MessageChannel::IsPumpingMessages() ||
4539 mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
4541 // Modal UI being displayed in windowless plugins.
4542 if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
4543 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4544 LRESULT res;
4545 if (IsAsyncResponseEvent(msg, res)) {
4546 ReplyMessage(res);
4548 return;
4551 // Handle certain sync plugin events sent to the parent which
4552 // trigger ipc calls that result in deadlocks.
4554 DWORD dwResult = 0;
4555 bool handled = false;
4557 switch (msg) {
4558 // Windowless flash sending WM_ACTIVATE events to the main window
4559 // via calls to ShowWindow.
4560 case WM_ACTIVATE:
4561 if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
4562 IsWindow((HWND)lParam)) {
4563 // Check for Adobe Reader X sync activate message from their
4564 // helper window and ignore. Fixes an annoying focus problem.
4565 if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
4566 ISMEX_SEND) {
4567 wchar_t szClass[10];
4568 HWND focusWnd = (HWND)lParam;
4569 if (IsWindowVisible(focusWnd) &&
4570 GetClassNameW(focusWnd, szClass,
4571 sizeof(szClass) / sizeof(char16_t)) &&
4572 !wcscmp(szClass, L"Edit") &&
4573 !WinUtils::IsOurProcessWindow(focusWnd)) {
4574 break;
4577 handled = true;
4579 break;
4580 // Plugins taking or losing focus triggering focus app messages.
4581 case WM_SETFOCUS:
4582 case WM_KILLFOCUS:
4583 // Windowed plugins that pass sys key events to defwndproc generate
4584 // WM_SYSCOMMAND events to the main window.
4585 case WM_SYSCOMMAND:
4586 // Windowed plugins that fire context menu selection events to parent
4587 // windows.
4588 case WM_CONTEXTMENU:
4589 // IME events fired as a result of synchronous focus changes
4590 case WM_IME_SETCONTEXT:
4591 handled = true;
4592 break;
4595 if (handled &&
4596 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4597 ReplyMessage(dwResult);
4601 /**************************************************************
4602 **************************************************************
4604 ** BLOCK: Native events
4606 ** Main Windows message handlers and OnXXX handlers for
4607 ** Windows event handling.
4609 **************************************************************
4610 **************************************************************/
4612 /**************************************************************
4614 * SECTION: Wind proc.
4616 * The main Windows event procedures and associated
4617 * message processing methods.
4619 **************************************************************/
4621 static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
4622 int32_t x, int32_t y) {
4623 HMENU hMenu = GetSystemMenu(hWnd, FALSE);
4624 if (hMenu) {
4625 MENUITEMINFO mii;
4626 mii.cbSize = sizeof(MENUITEMINFO);
4627 mii.fMask = MIIM_STATE;
4628 mii.fType = 0;
4630 // update the options
4631 mii.fState = MF_ENABLED;
4632 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4633 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4634 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4635 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4636 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4638 mii.fState = MF_GRAYED;
4639 switch (sizeMode) {
4640 case nsSizeMode_Fullscreen:
4641 // intentional fall through
4642 case nsSizeMode_Maximized:
4643 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4644 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4645 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4646 break;
4647 case nsSizeMode_Minimized:
4648 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4649 break;
4650 case nsSizeMode_Normal:
4651 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4652 break;
4653 case nsSizeMode_Invalid:
4654 NS_ASSERTION(false, "Did the argument come from invalid IPC?");
4655 break;
4656 default:
4657 MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected");
4658 break;
4660 LPARAM cmd = TrackPopupMenu(
4661 hMenu,
4662 (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN |
4663 (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
4664 x, y, 0, hWnd, nullptr);
4665 if (cmd) {
4666 PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
4667 return true;
4670 return false;
4673 // The WndProc procedure for all nsWindows in this toolkit. This merely catches
4674 // SEH exceptions and passes the real work to WindowProcInternal. See bug 587406
4675 // and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
4676 LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
4677 LPARAM lParam) {
4678 mozilla::ipc::CancelCPOWs();
4680 BackgroundHangMonitor().NotifyActivity();
4682 return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
4683 wParam, lParam);
4686 LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
4687 WPARAM wParam, LPARAM lParam) {
4688 if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
4689 // This message was sent to the FAKETRACKPOINTSCROLLABLE.
4690 if (msg == WM_HSCROLL) {
4691 // Route WM_HSCROLL messages to the main window.
4692 hWnd = ::GetParent(::GetParent(hWnd));
4693 } else {
4694 // Handle all other messages with its original window procedure.
4695 WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
4696 return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
4700 if (msg == MOZ_WM_TRACE) {
4701 // This is a tracer event for measuring event loop latency.
4702 // See WidgetTraceEvent.cpp for more details.
4703 mozilla::SignalTracerThread();
4704 return 0;
4707 // Get the window which caused the event and ask it to process the message
4708 nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
4709 NS_ASSERTION(targetWindow, "nsWindow* is null!");
4710 if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
4712 // Hold the window for the life of this method, in case it gets
4713 // destroyed during processing, unless we're in the dtor already.
4714 nsCOMPtr<nsIWidget> kungFuDeathGrip;
4715 if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
4717 targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
4719 // Create this here so that we store the last rolled up popup until after
4720 // the event has been processed.
4721 nsAutoRollup autoRollup;
4723 LRESULT popupHandlingResult;
4724 if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult))
4725 return popupHandlingResult;
4727 // Call ProcessMessage
4728 LRESULT retValue;
4729 if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
4730 return retValue;
4733 LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
4734 wParam, lParam);
4736 return res;
4739 const char16_t* GetQuitType() {
4740 if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
4741 DWORD cchCmdLine = 0;
4742 HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
4743 &cchCmdLine, nullptr);
4744 if (rc == S_OK) {
4745 return u"os-restart";
4748 return nullptr;
4751 bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
4752 LPARAM& aLParam,
4753 MSGResult& aResult) {
4754 if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
4755 return true;
4758 if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
4759 return true;
4762 if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
4763 aResult)) {
4764 return true;
4767 return false;
4770 // The main windows message processing method. Wraps ProcessMessageInternal so
4771 // we can log aRetValue.
4772 bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
4773 LRESULT* aRetValue) {
4774 // For some events we might change the parameter values, so log
4775 // before and after we process them.
4776 NativeEventLogger eventLogger("nsWindow", mWnd, msg, wParam, lParam);
4777 bool result = ProcessMessageInternal(msg, wParam, lParam, aRetValue);
4778 eventLogger.SetResult(*aRetValue, result);
4780 return result;
4783 // The main windows message processing method. Called by ProcessMessage.
4784 bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
4785 LRESULT* aRetValue) {
4786 MSGResult msgResult(aRetValue);
4787 if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
4788 return (msgResult.mConsumed || !mWnd);
4791 bool result = false; // call the default nsWindow proc
4792 *aRetValue = 0;
4794 // The DWM resize hack (see bug 1763981) causes us to process a number of
4795 // messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
4796 // would ordinarily result in a whole lot of internal state being updated.
4798 // Since we're supposed to end in the same state we started in (and since the
4799 // content shouldn't know about any of this nonsense), just discard any
4800 // messages synchronously dispatched from within the hack.
4801 if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
4802 return true;
4805 // Glass hit testing w/custom transparent margins.
4807 // FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
4808 // now.
4809 LRESULT dwmHitResult;
4810 if (mCustomNonClient &&
4811 DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
4812 *aRetValue = dwmHitResult;
4813 return true;
4816 // The preference whether to use a different keyboard layout for each
4817 // window is cached, and updating it will not take effect until the
4818 // next restart. We read the preference here and not upon WM_ACTIVATE to make
4819 // sure that this behavior is consistent. Otherwise, if the user changed the
4820 // preference before having ever lowered the window, the preference would take
4821 // effect immediately.
4822 static const bool sSwitchKeyboardLayout =
4823 Preferences::GetBool("intl.keyboard.per_window_layout", false);
4824 AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
4826 // (Large blocks of code should be broken out into OnEvent handlers.)
4827 switch (msg) {
4828 // WM_QUERYENDSESSION must be handled by all windows.
4829 // Otherwise Windows thinks the window can just be killed at will.
4830 case WM_QUERYENDSESSION: {
4831 // Ask around if it's ok to quit.
4832 nsCOMPtr<nsIObserverService> obsServ =
4833 mozilla::services::GetObserverService();
4834 nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
4835 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
4836 cancelQuitWrapper->SetData(false);
4838 const char16_t* quitType = GetQuitType();
4839 obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
4840 quitType);
4842 bool shouldCancelQuit;
4843 cancelQuitWrapper->GetData(&shouldCancelQuit);
4844 *aRetValue = !shouldCancelQuit;
4845 result = true;
4846 } break;
4848 case MOZ_WM_STARTA11Y:
4849 #if defined(ACCESSIBILITY)
4850 Unused << GetAccessible();
4851 result = true;
4852 #else
4853 result = false;
4854 #endif
4855 break;
4857 case WM_ENDSESSION: {
4858 // For WM_ENDSESSION, wParam indicates whether we need to shutdown
4859 // (TRUE) or not (FALSE).
4860 if (!wParam) {
4861 result = true;
4862 break;
4864 // According to WM_ENDSESSION lParam documentation:
4865 // 0 -> OS shutdown or restart (no way to distinguish)
4866 // ENDSESSION_LOGOFF -> User is logging off
4867 // ENDSESSION_CLOSEAPP -> Application must shutdown
4868 // ENDSESSION_CRITICAL -> Application is forced to shutdown
4869 // The difference of the last two is not very clear.
4870 if (lParam == 0) {
4871 shutdownReason = AppShutdownReason::OSShutdown;
4872 } else if (lParam & ENDSESSION_LOGOFF) {
4873 shutdownReason = AppShutdownReason::OSSessionEnd;
4874 } else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
4875 shutdownReason = AppShutdownReason::OSForceClose;
4876 } else {
4877 MOZ_DIAGNOSTIC_ASSERT(false,
4878 "Received WM_ENDSESSION with unknown flags.");
4879 shutdownReason = AppShutdownReason::OSForceClose;
4882 [[fallthrough]];
4883 case MOZ_WM_APP_QUIT: {
4884 if (shutdownReason == AppShutdownReason::Unknown) {
4885 // TODO: We do not expect that these days anybody sends us
4886 // MOZ_WM_APP_QUIT, see bug 1827807.
4887 shutdownReason = AppShutdownReason::WinUnexpectedMozQuit;
4889 // Let's fake a shutdown sequence without actually closing windows etc.
4890 // to avoid Windows killing us in the middle. A proper shutdown would
4891 // require having a chance to pump some messages. Unfortunately
4892 // Windows won't let us do that. Bug 212316.
4893 nsCOMPtr<nsIObserverService> obsServ =
4894 mozilla::services::GetObserverService();
4895 const char16_t* syncShutdown = u"syncShutdown";
4896 const char16_t* quitType = GetQuitType();
4898 AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
4900 obsServ->NotifyObservers(nullptr, "quit-application-granted",
4901 syncShutdown);
4902 obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
4904 AppShutdown::OnShutdownConfirmed();
4906 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
4907 quitType);
4908 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
4909 nullptr);
4910 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
4911 nullptr);
4912 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
4913 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
4914 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
4915 nullptr);
4917 AppShutdown::DoImmediateExit();
4918 MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
4919 } break;
4921 case WM_SYSCOLORCHANGE:
4922 // No need to invalidate layout for system color changes, but we need to
4923 // invalidate style.
4924 NotifyThemeChanged(widget::ThemeChangeKind::Style);
4925 break;
4927 case WM_THEMECHANGED: {
4928 // Update non-client margin offsets
4929 UpdateNonClientMargins();
4930 nsUXThemeData::UpdateNativeThemeInfo();
4932 // We assume pretty much everything could've changed here.
4933 NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
4935 UpdateDarkModeToolbar();
4937 // Invalidate the window so that the repaint will
4938 // pick up the new theme.
4939 Invalidate(true, true, true);
4940 } break;
4942 case WM_WTSSESSION_CHANGE: {
4943 switch (wParam) {
4944 case WTS_CONSOLE_CONNECT:
4945 case WTS_REMOTE_CONNECT:
4946 case WTS_SESSION_UNLOCK:
4947 // When a session becomes visible, we should invalidate.
4948 Invalidate(true, true, true);
4949 break;
4950 default:
4951 break;
4953 } break;
4955 case WM_FONTCHANGE: {
4956 // We only handle this message for the hidden window,
4957 // as we only need to update the (global) font list once
4958 // for any given change, not once per window!
4959 if (mWindowType != WindowType::Invisible) {
4960 break;
4963 // update the global font list
4964 gfxPlatform::GetPlatform()->UpdateFontList();
4965 } break;
4967 case WM_SETTINGCHANGE: {
4968 if (wParam == SPI_SETCLIENTAREAANIMATION ||
4969 wParam == SPI_SETKEYBOARDDELAY || wParam == SPI_SETMOUSEVANISH) {
4970 // These need to update LookAndFeel cached values.
4971 // They affect reduced motion settings / caret blink count / show
4972 // pointer while typing, so no need to invalidate style / layout.
4973 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
4974 break;
4976 if (wParam == SPI_SETFONTSMOOTHING ||
4977 wParam == SPI_SETFONTSMOOTHINGTYPE) {
4978 gfxDWriteFont::UpdateSystemTextVars();
4979 break;
4981 if (wParam == SPI_SETWORKAREA) {
4982 // NB: We also refresh screens on WM_DISPLAYCHANGE but the rcWork
4983 // values are sometimes wrong at that point. This message then
4984 // arrives soon afterward, when we can get the right rcWork values.
4985 ScreenHelperWin::RefreshScreens();
4986 break;
4988 if (auto lParamString = reinterpret_cast<const wchar_t*>(lParam)) {
4989 if (!wcscmp(lParamString, L"ImmersiveColorSet")) {
4990 // This affects system colors (-moz-win-accentcolor), so gotta pass
4991 // the style flag.
4992 NotifyThemeChanged(widget::ThemeChangeKind::Style);
4993 break;
4996 // UserInteractionMode, ConvertibleSlateMode, SystemDockMode may cause
4997 // @media(pointer) queries to change, which layout needs to know about
4999 // (WM_SETTINGCHANGE will be sent to all top-level windows, so we
5000 // only respond to the hidden top-level window to avoid hammering
5001 // layout with a bunch of NotifyThemeChanged() calls)
5003 if (mWindowType == WindowType::Invisible) {
5004 if (!wcscmp(lParamString, L"UserInteractionMode") ||
5005 !wcscmp(lParamString, L"ConvertibleSlateMode") ||
5006 !wcscmp(lParamString, L"SystemDockMode")) {
5007 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5008 WindowsUIUtils::UpdateInTabletMode();
5012 } break;
5014 case WM_DEVICECHANGE: {
5015 if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
5016 DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
5017 // Check dbch_devicetype explicitly since we will get other device types
5018 // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify
5019 // DBT_DEVTYP_DEVICEINTERFACE in the filter for
5020 // RegisterDeviceNotification.
5021 if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
5022 // This can only change media queries (any-hover/any-pointer).
5023 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5026 } break;
5028 case WM_NCCALCSIZE: {
5029 // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
5030 // will need to be kept in sync.
5031 if (mCustomNonClient) {
5032 // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
5033 // the proposed window rectangle for our window. During our
5034 // processing of the `WM_NCCALCSIZE` message, we are expected to
5035 // modify the `RECT` that `lParam` points to, so that its value upon
5036 // our return is the new client area. We must return 0 if `wParam`
5037 // is `FALSE`.
5039 // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
5040 // struct. This struct contains an array of 3 `RECT`s, the first of
5041 // which has the exact same meaning as the `RECT` that is pointed to
5042 // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
5043 // conjunction with our return value, can
5044 // be used to specify portions of the source and destination window
5045 // rectangles that are valid and should be preserved. We opt not to
5046 // implement an elaborate client-area preservation technique, and
5047 // simply return 0, which means "preserve the entire old client area
5048 // and align it with the upper-left corner of our new client area".
5049 RECT* clientRect =
5050 wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
5051 : (reinterpret_cast<RECT*>(lParam));
5052 auto margin = NonClientSizeMargin();
5053 clientRect->top += margin.top;
5054 clientRect->left += margin.left;
5055 clientRect->right -= margin.right;
5056 clientRect->bottom -= margin.bottom;
5057 // Make client rect's width and height more than 0 to
5058 // avoid problems of webrender and angle.
5059 clientRect->right = std::max(clientRect->right, clientRect->left + 1);
5060 clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
5062 result = true;
5063 *aRetValue = 0;
5065 break;
5068 case WM_NCHITTEST: {
5069 if (mInputRegion.mFullyTransparent) {
5070 // Treat this window as transparent.
5071 *aRetValue = HTTRANSPARENT;
5072 result = true;
5073 break;
5076 if (mInputRegion.mMargin) {
5077 const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
5078 GET_Y_LPARAM(lParam));
5079 LayoutDeviceIntRect screenRect = GetScreenBounds();
5080 screenRect.Deflate(mInputRegion.mMargin);
5081 if (!screenRect.Contains(screenPoint)) {
5082 *aRetValue = HTTRANSPARENT;
5083 result = true;
5084 break;
5089 * If an nc client area margin has been moved, we are responsible
5090 * for calculating where the resize margins are and returning the
5091 * appropriate set of hit test constants. DwmDefWindowProc (above)
5092 * will handle hit testing on it's command buttons if we are on a
5093 * composited desktop.
5096 if (!mCustomNonClient) {
5097 break;
5100 *aRetValue =
5101 ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5102 result = true;
5103 break;
5106 case WM_SETTEXT:
5108 * WM_SETTEXT paints the titlebar area. Avoid this if we have a
5109 * custom titlebar we paint ourselves, or if we're the ones
5110 * sending the message with an updated title
5113 if (mSendingSetText || !mCustomNonClient || mNonClientMargins.top == -1)
5114 break;
5117 // From msdn, the way around this is to disable the visible state
5118 // temporarily. We need the text to be set but we don't want the
5119 // redraw to occur. However, we need to make sure that we don't
5120 // do this at the same time that a Present is happening.
5122 // To do this we take mPresentLock in nsWindow::PreRender and
5123 // if that lock is taken we wait before doing WM_SETTEXT
5124 if (mCompositorWidgetDelegate) {
5125 mCompositorWidgetDelegate->EnterPresentLock();
5127 DWORD style = GetWindowLong(mWnd, GWL_STYLE);
5128 SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
5129 *aRetValue =
5130 CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
5131 SetWindowLong(mWnd, GWL_STYLE, style);
5132 if (mCompositorWidgetDelegate) {
5133 mCompositorWidgetDelegate->LeavePresentLock();
5136 return true;
5139 case WM_NCACTIVATE: {
5141 * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting
5142 * through WM_NCPAINT via InvalidateNonClientRegion.
5144 UpdateGetWindowInfoCaptionStatus(FALSE != wParam);
5146 if (!mCustomNonClient) {
5147 break;
5150 // There is a case that rendered result is not kept. Bug 1237617
5151 if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
5152 NS_DispatchToMainThread(NewRunnableMethod(
5153 "nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
5156 // let the dwm handle nc painting on glass
5157 // Never allow native painting if we are on fullscreen
5158 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) break;
5160 if (wParam == TRUE) {
5161 // going active
5162 *aRetValue = FALSE; // ignored
5163 result = true;
5164 // invalidate to trigger a paint
5165 InvalidateNonClientRegion();
5166 break;
5167 } else {
5168 // going inactive
5169 *aRetValue = TRUE; // go ahead and deactive
5170 result = true;
5171 // invalidate to trigger a paint
5172 InvalidateNonClientRegion();
5173 break;
5177 case WM_NCPAINT: {
5179 * ClearType changes often don't send a WM_SETTINGCHANGE message. But they
5180 * do seem to always send a WM_NCPAINT message, so let's update on that.
5182 gfxDWriteFont::UpdateSystemTextVars();
5183 } break;
5185 case WM_POWERBROADCAST:
5186 switch (wParam) {
5187 case PBT_APMSUSPEND:
5188 PostSleepWakeNotification(true);
5189 break;
5190 case PBT_APMRESUMEAUTOMATIC:
5191 case PBT_APMRESUMECRITICAL:
5192 case PBT_APMRESUMESUSPEND:
5193 PostSleepWakeNotification(false);
5194 break;
5196 break;
5198 case WM_CLOSE: // close request
5199 if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
5200 result = true; // abort window closure
5201 break;
5203 case WM_DESTROY:
5204 // clean up.
5205 DestroyLayerManager();
5206 OnDestroy();
5207 result = true;
5208 break;
5210 case WM_PAINT:
5211 *aRetValue = (int)OnPaint(0);
5212 result = true;
5213 break;
5215 case WM_HOTKEY:
5216 result = OnHotKey(wParam, lParam);
5217 break;
5219 case WM_SYSCHAR:
5220 case WM_CHAR: {
5221 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5222 result = ProcessCharMessage(nativeMsg, nullptr);
5223 DispatchPendingEvents();
5224 } break;
5226 case WM_SYSKEYUP:
5227 case WM_KEYUP: {
5228 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5229 nativeMsg.time = ::GetMessageTime();
5230 result = ProcessKeyUpMessage(nativeMsg, nullptr);
5231 DispatchPendingEvents();
5232 } break;
5234 case WM_SYSKEYDOWN:
5235 case WM_KEYDOWN: {
5236 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5237 result = ProcessKeyDownMessage(nativeMsg, nullptr);
5238 DispatchPendingEvents();
5239 } break;
5241 // Say we've dealt with erasing the background. (This is actually handled in
5242 // WM_PAINT, where necessary.)
5243 case WM_ERASEBKGND: {
5244 *aRetValue = 1;
5245 result = true;
5246 } break;
5248 case WM_MOUSEMOVE: {
5249 LPARAM lParamScreen = lParamToScreen(lParam);
5250 mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
5251 GET_Y_LPARAM(lParamScreen));
5253 if (!mMousePresent && !sIsInMouseCapture) {
5254 // First MOUSEMOVE over the client area. Ask for MOUSELEAVE
5255 TRACKMOUSEEVENT mTrack;
5256 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5257 mTrack.dwFlags = TME_LEAVE;
5258 mTrack.dwHoverTime = 0;
5259 mTrack.hwndTrack = mWnd;
5260 TrackMouseEvent(&mTrack);
5262 mMousePresent = true;
5264 // Suppress dispatch of pending events
5265 // when mouse moves are generated by widget
5266 // creation instead of user input.
5267 POINT mp;
5268 mp.x = GET_X_LPARAM(lParamScreen);
5269 mp.y = GET_Y_LPARAM(lParamScreen);
5270 bool userMovedMouse = false;
5271 if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
5272 userMovedMouse = true;
5275 if (userMovedMouse) {
5276 result = DispatchMouseEvent(
5277 eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
5278 MOUSE_INPUT_SOURCE(),
5279 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5280 DispatchPendingEvents();
5282 } break;
5284 case WM_NCMOUSEMOVE: {
5285 LPARAM lParamClient = lParamToClient(lParam);
5286 if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
5287 if (!sIsInMouseCapture) {
5288 TRACKMOUSEEVENT mTrack;
5289 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5290 mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
5291 mTrack.dwHoverTime = 0;
5292 mTrack.hwndTrack = mWnd;
5293 TrackMouseEvent(&mTrack);
5295 // If we noticed the mouse moving in our draggable region, forward the
5296 // message as a normal WM_MOUSEMOVE.
5297 SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
5298 } else {
5299 // We've transitioned from a draggable area to somewhere else within
5300 // the non-client area - perhaps one of the edges of the window for
5301 // resizing.
5302 mSimulatedClientArea = false;
5305 if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
5306 SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
5308 } break;
5310 case WM_LBUTTONDOWN: {
5311 result =
5312 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5313 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5314 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5315 DispatchPendingEvents();
5316 } break;
5318 case WM_LBUTTONUP: {
5319 result =
5320 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5321 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5322 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5323 DispatchPendingEvents();
5324 } break;
5326 case WM_NCMOUSELEAVE: {
5327 mSimulatedClientArea = false;
5329 if (EventIsInsideWindow(this)) {
5330 // If we're handling WM_NCMOUSELEAVE and the mouse is still over the
5331 // window, then by process of elimination, the mouse has moved from the
5332 // non-client to client area, so no need to fall-through to the
5333 // WM_MOUSELEAVE handler. We also need to re-register for the
5334 // WM_MOUSELEAVE message, since according to the documentation at [1],
5335 // all tracking requested via TrackMouseEvent is cleared once
5336 // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
5337 // [1]:
5338 // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
5339 TRACKMOUSEEVENT mTrack;
5340 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5341 mTrack.dwFlags = TME_LEAVE;
5342 mTrack.dwHoverTime = 0;
5343 mTrack.hwndTrack = mWnd;
5344 TrackMouseEvent(&mTrack);
5345 break;
5347 // We've transitioned from non-client to outside of the window, so
5348 // fall-through to the WM_MOUSELEAVE handler.
5349 [[fallthrough]];
5351 case WM_MOUSELEAVE: {
5352 if (!mMousePresent) break;
5353 if (mSimulatedClientArea) break;
5354 mMousePresent = false;
5356 // Check if the mouse is over the fullscreen transition window, if so
5357 // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
5358 // transition window disappears will not be ignored, even if the mouse
5359 // hasn't moved.
5360 if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
5361 sLastMouseMovePoint = {0};
5364 // We need to check mouse button states and put them in for
5365 // wParam.
5366 WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
5367 (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
5368 (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
5369 // Synthesize an event position because we don't get one from
5370 // WM_MOUSELEAVE.
5371 LPARAM pos = lParamToClient(::GetMessagePos());
5372 DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
5373 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5374 } break;
5376 case WM_CONTEXTMENU: {
5377 // If the context menu is brought up by a touch long-press, then
5378 // the APZ code is responsible for dealing with this, so we don't
5379 // need to do anything.
5380 if (mTouchWindow &&
5381 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
5382 MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
5383 result = true;
5384 break;
5387 // If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
5388 // event in overscroll gutter, we shouldn't open context menu.
5389 if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
5390 mNeedsToPreventContextMenu) {
5391 result = true;
5392 break;
5395 // if the context menu is brought up from the keyboard, |lParam|
5396 // will be -1.
5397 LPARAM pos;
5398 bool contextMenukey = false;
5399 if (lParam == -1) {
5400 contextMenukey = true;
5401 pos = lParamToClient(GetMessagePos());
5402 } else {
5403 pos = lParamToClient(lParam);
5406 result = DispatchMouseEvent(
5407 eContextMenu, wParam, pos, contextMenukey,
5408 contextMenukey ? MouseButton::ePrimary : MouseButton::eSecondary,
5409 MOUSE_INPUT_SOURCE());
5410 if (lParam != -1 && !result && mCustomNonClient &&
5411 mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
5412 // Blank area hit, throw up the system menu.
5413 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5414 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5415 result = true;
5417 } break;
5419 case WM_POINTERLEAVE:
5420 case WM_POINTERDOWN:
5421 case WM_POINTERUP:
5422 case WM_POINTERUPDATE:
5423 result = OnPointerEvents(msg, wParam, lParam);
5424 if (result) {
5425 DispatchPendingEvents();
5427 break;
5429 case DM_POINTERHITTEST:
5430 if (mDmOwner) {
5431 UINT contactId = GET_POINTERID_WPARAM(wParam);
5432 POINTER_INPUT_TYPE pointerType;
5433 if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
5434 pointerType == PT_TOUCHPAD) {
5435 mDmOwner->SetContact(contactId);
5438 break;
5440 case WM_LBUTTONDBLCLK:
5441 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5442 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5443 DispatchPendingEvents();
5444 break;
5446 case WM_MBUTTONDOWN:
5447 result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5448 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5449 DispatchPendingEvents();
5450 break;
5452 case WM_MBUTTONUP:
5453 result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5454 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5455 DispatchPendingEvents();
5456 break;
5458 case WM_MBUTTONDBLCLK:
5459 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5460 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5461 DispatchPendingEvents();
5462 break;
5464 case WM_NCMBUTTONDOWN:
5465 result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5466 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5467 DispatchPendingEvents();
5468 break;
5470 case WM_NCMBUTTONUP:
5471 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5472 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5473 DispatchPendingEvents();
5474 break;
5476 case WM_NCMBUTTONDBLCLK:
5477 result =
5478 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5479 false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5480 DispatchPendingEvents();
5481 break;
5483 case WM_RBUTTONDOWN:
5484 result =
5485 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5486 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5487 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5488 DispatchPendingEvents();
5489 break;
5491 case WM_RBUTTONUP:
5492 result =
5493 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5494 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5495 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5496 DispatchPendingEvents();
5497 break;
5499 case WM_RBUTTONDBLCLK:
5500 result =
5501 DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5502 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5503 DispatchPendingEvents();
5504 break;
5506 case WM_NCRBUTTONDOWN:
5507 result =
5508 DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5509 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5510 DispatchPendingEvents();
5511 break;
5513 case WM_NCRBUTTONUP:
5514 result =
5515 DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5516 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5517 DispatchPendingEvents();
5518 break;
5520 case WM_NCRBUTTONDBLCLK:
5521 result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5522 false, MouseButton::eSecondary,
5523 MOUSE_INPUT_SOURCE());
5524 DispatchPendingEvents();
5525 break;
5527 // Windows doesn't provide to customize the behavior of 4th nor 5th button
5528 // of mouse. If 5-button mouse works with standard mouse deriver of
5529 // Windows, users cannot disable 4th button (browser back) nor 5th button
5530 // (browser forward). We should allow to do it with our prefs since we can
5531 // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
5532 // messages are not sent to DefWindowProc.
5533 case WM_XBUTTONDOWN:
5534 case WM_XBUTTONUP:
5535 case WM_NCXBUTTONDOWN:
5536 case WM_NCXBUTTONUP:
5537 *aRetValue = TRUE;
5538 switch (GET_XBUTTON_WPARAM(wParam)) {
5539 case XBUTTON1:
5540 result = !Preferences::GetBool("mousebutton.4th.enabled", true);
5541 break;
5542 case XBUTTON2:
5543 result = !Preferences::GetBool("mousebutton.5th.enabled", true);
5544 break;
5545 default:
5546 break;
5548 break;
5550 case WM_SIZING: {
5551 if (mAspectRatio > 0) {
5552 LPRECT rect = (LPRECT)lParam;
5553 int32_t newWidth, newHeight;
5555 // The following conditions and switch statement borrow heavily from the
5556 // Chromium source code from
5557 // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
5558 if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
5559 wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
5560 newWidth = rect->right - rect->left;
5561 newHeight = newWidth / mAspectRatio;
5562 if (newHeight < mSizeConstraints.mMinSize.height) {
5563 newHeight = mSizeConstraints.mMinSize.height;
5564 newWidth = newHeight * mAspectRatio;
5565 } else if (newHeight > mSizeConstraints.mMaxSize.height) {
5566 newHeight = mSizeConstraints.mMaxSize.height;
5567 newWidth = newHeight * mAspectRatio;
5569 } else {
5570 newHeight = rect->bottom - rect->top;
5571 newWidth = newHeight * mAspectRatio;
5572 if (newWidth < mSizeConstraints.mMinSize.width) {
5573 newWidth = mSizeConstraints.mMinSize.width;
5574 newHeight = newWidth / mAspectRatio;
5575 } else if (newWidth > mSizeConstraints.mMaxSize.width) {
5576 newWidth = mSizeConstraints.mMaxSize.width;
5577 newHeight = newWidth / mAspectRatio;
5581 switch (wParam) {
5582 case WMSZ_RIGHT:
5583 case WMSZ_BOTTOM:
5584 rect->right = newWidth + rect->left;
5585 rect->bottom = rect->top + newHeight;
5586 break;
5587 case WMSZ_TOP:
5588 rect->right = newWidth + rect->left;
5589 rect->top = rect->bottom - newHeight;
5590 break;
5591 case WMSZ_LEFT:
5592 case WMSZ_TOPLEFT:
5593 rect->left = rect->right - newWidth;
5594 rect->top = rect->bottom - newHeight;
5595 break;
5596 case WMSZ_TOPRIGHT:
5597 rect->right = rect->left + newWidth;
5598 rect->top = rect->bottom - newHeight;
5599 break;
5600 case WMSZ_BOTTOMLEFT:
5601 rect->left = rect->right - newWidth;
5602 rect->bottom = rect->top + newHeight;
5603 break;
5604 case WMSZ_BOTTOMRIGHT:
5605 rect->right = rect->left + newWidth;
5606 rect->bottom = rect->top + newHeight;
5607 break;
5611 // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
5612 // resize or move event. Instead we wait for first VM_SIZING message
5613 // within a ENTERSIZEMOVE to consider this a live resize event.
5614 if (mResizeState == IN_SIZEMOVE) {
5615 mResizeState = RESIZING;
5616 NotifyLiveResizeStarted();
5618 break;
5621 case WM_MOVING:
5622 FinishLiveResizing(MOVING);
5623 if (WinUtils::IsPerMonitorDPIAware()) {
5624 // Sometimes, we appear to miss a WM_DPICHANGED message while moving
5625 // a window around. Therefore, call ChangedDPI and ResetLayout here
5626 // if it appears that the window's scaling is not what we expect.
5627 // This causes the prescontext and appshell window management code to
5628 // check the appUnitsPerDevPixel value and current widget size, and
5629 // refresh them if necessary. If nothing has changed, these calls will
5630 // return without actually triggering any extra reflow or painting.
5631 if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
5632 ChangedDPI();
5633 ResetLayout();
5634 if (mWidgetListener) {
5635 mWidgetListener->UIResolutionChanged();
5639 break;
5641 case WM_ENTERSIZEMOVE: {
5642 if (mResizeState == NOT_RESIZING) {
5643 mResizeState = IN_SIZEMOVE;
5645 break;
5648 case WM_EXITSIZEMOVE: {
5649 FinishLiveResizing(NOT_RESIZING);
5651 if (!sIsInMouseCapture) {
5652 NotifySizeMoveDone();
5655 // Windows spins a separate hidden event loop when moving a window so we
5656 // don't hear mouse events during this time and WM_EXITSIZEMOVE is fired
5657 // when the hidden event loop exits. We set mDraggingWindowWithMouse to
5658 // true in WM_NCLBUTTONDOWN when we started moving the window with the
5659 // mouse so we know that if mDraggingWindowWithMouse is true, we can send
5660 // a mouse up event.
5661 if (mDraggingWindowWithMouse) {
5662 mDraggingWindowWithMouse = false;
5663 result = DispatchMouseEvent(
5664 eMouseUp, wParam, lParam, false, MouseButton::ePrimary,
5665 MOUSE_INPUT_SOURCE(),
5666 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5669 break;
5672 case WM_DISPLAYCHANGE: {
5673 ScreenHelperWin::RefreshScreens();
5674 if (mWidgetListener) {
5675 mWidgetListener->UIResolutionChanged();
5677 break;
5680 case WM_NCLBUTTONDBLCLK:
5681 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
5682 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5683 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5684 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5685 DispatchPendingEvents();
5686 break;
5688 case WM_NCLBUTTONDOWN: {
5689 // Dispatch a custom event when this happens in the draggable region, so
5690 // that non-popup-based panels can react to it. This doesn't send an
5691 // actual mousedown event because that would break dragging or interfere
5692 // with other mousedown handling in the caption area.
5693 if (ClientMarginHitTestPoint(GET_X_LPARAM(lParam),
5694 GET_Y_LPARAM(lParam)) == HTCAPTION) {
5695 DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
5696 mDraggingWindowWithMouse = true;
5699 if (IsWindowButton(wParam) && mCustomNonClient) {
5700 DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
5701 lParamToClient(lParam), false, MouseButton::ePrimary,
5702 MOUSE_INPUT_SOURCE(), nullptr, true);
5703 DispatchPendingEvents();
5704 result = true;
5706 break;
5709 case WM_APPCOMMAND: {
5710 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5711 result = HandleAppCommandMsg(nativeMsg, aRetValue);
5712 break;
5715 // The WM_ACTIVATE event is fired when a window is raised or lowered,
5716 // and the loword of wParam specifies which. But we don't want to tell
5717 // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
5718 // events are fired. Instead, set either the sJustGotActivate or
5719 // gJustGotDeactivate flags and activate/deactivate once the focus
5720 // events arrive.
5721 case WM_ACTIVATE: {
5722 int32_t fActive = LOWORD(wParam);
5723 if (mWidgetListener) {
5724 if (WA_INACTIVE == fActive) {
5725 // when minimizing a window, the deactivation and focus events will
5726 // be fired in the reverse order. Instead, just deactivate right away.
5727 // This can also happen when a modal system dialog is opened, so check
5728 // if the last window to receive the WM_KILLFOCUS message was this one
5729 // or a child of this one.
5730 if (HIWORD(wParam) ||
5731 (mLastKillFocusWindow &&
5732 (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
5733 DispatchFocusToTopLevelWindow(false);
5734 } else {
5735 sJustGotDeactivate = true;
5737 if (mIsTopWidgetWindow) {
5738 mLastKeyboardLayout = KeyboardLayout::GetInstance()->GetLayout();
5740 } else {
5741 StopFlashing();
5743 sJustGotActivate = true;
5744 WidgetMouseEvent event(true, eMouseActivate, this,
5745 WidgetMouseEvent::eReal);
5746 InitEvent(event);
5747 ModifierKeyState modifierKeyState;
5748 modifierKeyState.InitInputEvent(event);
5749 DispatchInputEvent(&event);
5750 if (sSwitchKeyboardLayout && mLastKeyboardLayout)
5751 ActivateKeyboardLayout(mLastKeyboardLayout, 0);
5753 #ifdef ACCESSIBILITY
5754 a11y::LazyInstantiator::ResetUiaDetectionCache();
5755 #endif
5758 } break;
5760 case WM_ACTIVATEAPP: {
5761 // Bug 1851991: Sometimes this can be called before gfxPlatform::Init
5762 // when a window is created very early. In that case we just forego
5763 // setting this and accept the GPU process might briefly run at a lower
5764 // priority.
5765 if (GPUProcessManager::Get()) {
5766 GPUProcessManager::Get()->SetAppInForeground(wParam);
5768 } break;
5770 case WM_MOUSEACTIVATE:
5771 // A popup with a parent owner should not be activated when clicked but
5772 // should still allow the mouse event to be fired, so the return value
5773 // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
5774 // just use default processing so that the window is activated.
5775 if (IsPopup() && IsOwnerForegroundWindow()) {
5776 *aRetValue = MA_NOACTIVATE;
5777 result = true;
5779 break;
5781 case WM_WINDOWPOSCHANGING: {
5782 LPWINDOWPOS info = (LPWINDOWPOS)lParam;
5783 OnWindowPosChanging(info);
5784 result = true;
5785 } break;
5787 // Workaround for race condition in explorer.exe.
5788 case MOZ_WM_FULLSCREEN_STATE_UPDATE: {
5789 TaskbarConcealer::OnAsyncStateUpdateRequest(mWnd);
5790 result = true;
5791 } break;
5793 case WM_GETMINMAXINFO: {
5794 MINMAXINFO* mmi = (MINMAXINFO*)lParam;
5795 // Set the constraints. The minimum size should also be constrained to the
5796 // default window maximum size so that it fits on screen.
5797 mmi->ptMinTrackSize.x =
5798 std::min((int32_t)mmi->ptMaxTrackSize.x,
5799 std::max((int32_t)mmi->ptMinTrackSize.x,
5800 mSizeConstraints.mMinSize.width));
5801 mmi->ptMinTrackSize.y =
5802 std::min((int32_t)mmi->ptMaxTrackSize.y,
5803 std::max((int32_t)mmi->ptMinTrackSize.y,
5804 mSizeConstraints.mMinSize.height));
5805 mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
5806 mSizeConstraints.mMaxSize.width);
5807 mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
5808 mSizeConstraints.mMaxSize.height);
5809 } break;
5811 case WM_SETFOCUS: {
5812 WndProcUrgentInvocation::Marker _marker;
5814 // If previous focused window isn't ours, it must have received the
5815 // redirected message. So, we should forget it.
5816 if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
5817 RedirectedKeyDownMessageManager::Forget();
5819 if (sJustGotActivate) {
5820 DispatchFocusToTopLevelWindow(true);
5822 TaskbarConcealer::OnFocusAcquired(this);
5823 } break;
5825 case WM_KILLFOCUS:
5826 if (sJustGotDeactivate) {
5827 DispatchFocusToTopLevelWindow(false);
5828 } else {
5829 mLastKillFocusWindow = mWnd;
5831 break;
5833 case WM_WINDOWPOSCHANGED: {
5834 WINDOWPOS* wp = (LPWINDOWPOS)lParam;
5835 OnWindowPosChanged(wp);
5836 TaskbarConcealer::OnWindowPosChanged(this);
5837 result = true;
5838 } break;
5840 case WM_INPUTLANGCHANGEREQUEST:
5841 *aRetValue = TRUE;
5842 result = false;
5843 break;
5845 case WM_INPUTLANGCHANGE:
5846 KeyboardLayout::GetInstance()->OnLayoutChange(
5847 reinterpret_cast<HKL>(lParam));
5848 nsBidiKeyboard::OnLayoutChange();
5849 result = false; // always pass to child window
5850 break;
5852 case WM_DESTROYCLIPBOARD: {
5853 nsIClipboard* clipboard;
5854 nsresult rv = CallGetService(kCClipboardCID, &clipboard);
5855 if (NS_SUCCEEDED(rv)) {
5856 clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
5857 NS_RELEASE(clipboard);
5859 } break;
5861 #ifdef ACCESSIBILITY
5862 case WM_GETOBJECT: {
5863 *aRetValue = 0;
5864 // Do explicit casting to make it working on 64bit systems (see bug 649236
5865 // for details).
5866 int32_t objId = static_cast<DWORD>(lParam);
5867 if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
5868 RefPtr<IAccessible> root(
5869 a11y::LazyInstantiator::GetRootAccessible(mWnd));
5870 if (root) {
5871 *aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
5872 a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
5873 result = true;
5876 } break;
5877 #endif
5879 case WM_SYSCOMMAND: {
5880 WPARAM const filteredWParam = (wParam & 0xFFF0);
5882 // SC_CLOSE may trigger a synchronous confirmation prompt. If we're in the
5883 // middle of something important, put off responding to it.
5884 if (filteredWParam == SC_CLOSE && WndProcUrgentInvocation::IsActive()) {
5885 ::PostMessageW(mWnd, msg, wParam, lParam);
5886 result = true;
5887 break;
5890 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
5891 filteredWParam == SC_RESTORE &&
5892 GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
5893 mFrameState->EnsureFullscreenMode(false);
5894 result = true;
5897 // Handle the system menu manually when we're in full screen mode
5898 // so we can set the appropriate options.
5899 if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE &&
5900 mFrameState->GetSizeMode() == nsSizeMode_Fullscreen) {
5901 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5902 MOZ_SYSCONTEXT_X_POS, MOZ_SYSCONTEXT_Y_POS);
5903 result = true;
5905 } break;
5907 case WM_DPICHANGED: {
5908 LPRECT rect = (LPRECT)lParam;
5909 OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
5910 rect->bottom - rect->top);
5911 break;
5914 /* Gesture support events */
5915 case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
5916 // According to MS samples, this must be handled to enable
5917 // rotational support in multi-touch drivers.
5918 result = true;
5919 *aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
5920 break;
5922 case WM_TOUCH:
5923 result = OnTouch(wParam, lParam);
5924 if (result) {
5925 *aRetValue = 0;
5927 break;
5929 case WM_GESTURE:
5930 result = OnGesture(wParam, lParam);
5931 break;
5933 case WM_GESTURENOTIFY: {
5934 if (mWindowType != WindowType::Invisible) {
5935 // A GestureNotify event is dispatched to decide which single-finger
5936 // panning direction should be active (including none) and if pan
5937 // feedback should be displayed. Java and plugin windows can make their
5938 // own calls.
5940 GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
5941 nsPointWin touchPoint;
5942 touchPoint = gestureinfo->ptsLocation;
5943 touchPoint.ScreenToClient(mWnd);
5944 WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
5945 gestureNotifyEvent.mRefPoint =
5946 LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
5947 nsEventStatus status;
5948 DispatchEvent(&gestureNotifyEvent, status);
5949 mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
5950 if (!mTouchWindow)
5951 mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
5953 result = false; // should always bubble to DefWindowProc
5954 } break;
5956 case WM_CLEAR: {
5957 WidgetContentCommandEvent command(true, eContentCommandDelete, this);
5958 DispatchWindowEvent(command);
5959 result = true;
5960 } break;
5962 case WM_CUT: {
5963 WidgetContentCommandEvent command(true, eContentCommandCut, this);
5964 DispatchWindowEvent(command);
5965 result = true;
5966 } break;
5968 case WM_COPY: {
5969 WidgetContentCommandEvent command(true, eContentCommandCopy, this);
5970 DispatchWindowEvent(command);
5971 result = true;
5972 } break;
5974 case WM_PASTE: {
5975 WidgetContentCommandEvent command(true, eContentCommandPaste, this);
5976 DispatchWindowEvent(command);
5977 result = true;
5978 } break;
5980 case EM_UNDO: {
5981 WidgetContentCommandEvent command(true, eContentCommandUndo, this);
5982 DispatchWindowEvent(command);
5983 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
5984 result = true;
5985 } break;
5987 case EM_REDO: {
5988 WidgetContentCommandEvent command(true, eContentCommandRedo, this);
5989 DispatchWindowEvent(command);
5990 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
5991 result = true;
5992 } break;
5994 case EM_CANPASTE: {
5995 // Support EM_CANPASTE message only when wParam isn't specified or
5996 // is plain text format.
5997 if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
5998 WidgetContentCommandEvent command(true, eContentCommandPaste, this,
5999 true);
6000 DispatchWindowEvent(command);
6001 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6002 result = true;
6004 } break;
6006 case EM_CANUNDO: {
6007 WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
6008 DispatchWindowEvent(command);
6009 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6010 result = true;
6011 } break;
6013 case EM_CANREDO: {
6014 WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
6015 DispatchWindowEvent(command);
6016 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6017 result = true;
6018 } break;
6020 case MOZ_WM_SKEWFIX: {
6021 TimeStamp skewStamp;
6022 if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
6023 &skewStamp)) {
6024 TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
6025 skewStamp);
6027 } break;
6029 default: {
6030 if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
6031 SetHasTaskbarIconBeenCreated();
6033 } break;
6036 //*aRetValue = result;
6037 if (mWnd) {
6038 return result;
6039 } else {
6040 // Events which caused mWnd destruction and aren't consumed
6041 // will crash during the Windows default processing.
6042 return true;
6046 void nsWindow::FinishLiveResizing(ResizeState aNewState) {
6047 if (mResizeState == RESIZING) {
6048 NotifyLiveResizeStopped();
6050 mResizeState = aNewState;
6051 ForcePresent();
6054 /**************************************************************
6056 * SECTION: Event processing helpers
6058 * Special processing for certain event types and
6059 * synthesized events.
6061 **************************************************************/
6063 LayoutDeviceIntMargin nsWindow::NonClientSizeMargin(
6064 const LayoutDeviceIntMargin& aNonClientOffset) const {
6065 return LayoutDeviceIntMargin(mCaptionHeight - aNonClientOffset.top,
6066 mHorResizeMargin - aNonClientOffset.right,
6067 mVertResizeMargin - aNonClientOffset.bottom,
6068 mHorResizeMargin - aNonClientOffset.left);
6071 int32_t nsWindow::ClientMarginHitTestPoint(int32_t aX, int32_t aY) {
6072 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
6073 if (sizeMode == nsSizeMode_Minimized || sizeMode == nsSizeMode_Fullscreen) {
6074 return HTCLIENT;
6077 // Calculations are done in screen coords
6078 const LayoutDeviceIntRect winRect = GetScreenBounds();
6079 const LayoutDeviceIntPoint point(aX, aY);
6081 // hit return constants:
6082 // HTBORDER - non-resizable border
6083 // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
6084 // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
6085 // HTTOPLEFT, HTTOPRIGHT - resizable corner
6086 // HTCAPTION - general title bar area
6087 // HTCLIENT - area considered the client
6088 // HTCLOSE - hovering over the close button
6089 // HTMAXBUTTON - maximize button
6090 // HTMINBUTTON - minimize button
6092 int32_t testResult = HTCLIENT;
6093 const bool isResizable =
6094 sizeMode != nsSizeMode_Maximized &&
6095 (mBorderStyle &
6096 (BorderStyle::All | BorderStyle::ResizeH | BorderStyle::Default));
6098 LayoutDeviceIntMargin nonClientSizeMargin = NonClientSizeMargin();
6100 // Ensure being accessible to borders of window. Even if contents are in
6101 // this area, the area must behave as border.
6102 nonClientSizeMargin.EnsureAtLeast(
6103 LayoutDeviceIntMargin(kResizableBorderMinSize, kResizableBorderMinSize,
6104 kResizableBorderMinSize, kResizableBorderMinSize));
6106 LayoutDeviceIntRect clientRect = winRect;
6107 clientRect.Deflate(nonClientSizeMargin);
6109 const bool allowContentOverride =
6110 sizeMode == nsSizeMode_Maximized || clientRect.Contains(point);
6112 // The border size. If there is no content under mouse cursor, the border
6113 // size should be larger than the values in system settings. Otherwise,
6114 // contents under the mouse cursor should be able to override the behavior.
6115 // E.g., user must expect that Firefox button always opens the popup menu
6116 // even when the user clicks on the above edge of it.
6117 LayoutDeviceIntMargin borderSize = nonClientSizeMargin;
6118 borderSize.EnsureAtLeast(
6119 LayoutDeviceIntMargin(mVertResizeMargin, mHorResizeMargin,
6120 mVertResizeMargin, mHorResizeMargin));
6122 bool top = false;
6123 bool bottom = false;
6124 bool left = false;
6125 bool right = false;
6127 if (point.y >= winRect.y && point.y < winRect.y + borderSize.top) {
6128 top = true;
6129 } else if (point.y <= winRect.YMost() &&
6130 point.y > winRect.YMost() - borderSize.bottom) {
6131 bottom = true;
6134 // (the 2x case here doubles the resize area for corners)
6135 int multiplier = (top || bottom) ? 2 : 1;
6136 if (point.x >= winRect.x &&
6137 point.x < winRect.x + (multiplier * borderSize.left)) {
6138 left = true;
6139 } else if (point.x <= winRect.XMost() &&
6140 point.x > winRect.XMost() - (multiplier * borderSize.right)) {
6141 right = true;
6144 bool inResizeRegion = false;
6145 if (isResizable) {
6146 if (top) {
6147 testResult = HTTOP;
6148 if (left) {
6149 testResult = HTTOPLEFT;
6150 } else if (right) {
6151 testResult = HTTOPRIGHT;
6153 } else if (bottom) {
6154 testResult = HTBOTTOM;
6155 if (left) {
6156 testResult = HTBOTTOMLEFT;
6157 } else if (right) {
6158 testResult = HTBOTTOMRIGHT;
6160 } else {
6161 if (left) {
6162 testResult = HTLEFT;
6164 if (right) {
6165 testResult = HTRIGHT;
6168 inResizeRegion = (testResult != HTCLIENT);
6169 } else {
6170 if (top) {
6171 testResult = HTCAPTION;
6172 } else if (bottom || left || right) {
6173 testResult = HTBORDER;
6177 if (!sIsInMouseCapture && allowContentOverride) {
6179 POINT pt = {aX, aY};
6180 ::ScreenToClient(mWnd, &pt);
6182 if (pt.x == mCachedHitTestPoint.x.value &&
6183 pt.y == mCachedHitTestPoint.y.value &&
6184 TimeStamp::Now() - mCachedHitTestTime <
6185 TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
6186 return mCachedHitTestResult;
6189 mCachedHitTestPoint = {pt.x, pt.y};
6190 mCachedHitTestTime = TimeStamp::Now();
6193 auto pt = mCachedHitTestPoint;
6195 if (mWindowBtnRect[WindowButtonType::Minimize].Contains(pt)) {
6196 testResult = HTMINBUTTON;
6197 } else if (mWindowBtnRect[WindowButtonType::Maximize].Contains(pt)) {
6198 testResult = HTMAXBUTTON;
6199 } else if (mWindowBtnRect[WindowButtonType::Close].Contains(pt)) {
6200 testResult = HTCLOSE;
6201 } else if (!inResizeRegion) {
6202 // If we're in the resize region, avoid overriding that with either a
6203 // drag or a client result; resize takes priority over either (but not
6204 // over the window controls, which is why we check this after those).
6205 if (mDraggableRegion.Contains(pt)) {
6206 testResult = HTCAPTION;
6207 } else {
6208 testResult = HTCLIENT;
6212 mCachedHitTestResult = testResult;
6215 return testResult;
6218 bool nsWindow::IsSimulatedClientArea(int32_t screenX, int32_t screenY) {
6219 int32_t testResult = ClientMarginHitTestPoint(screenX, screenY);
6220 return testResult == HTCAPTION || IsWindowButton(testResult);
6223 bool nsWindow::IsWindowButton(int32_t hitTestResult) {
6224 return hitTestResult == HTMINBUTTON || hitTestResult == HTMAXBUTTON ||
6225 hitTestResult == HTCLOSE;
6228 TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
6229 CurrentWindowsTimeGetter getCurrentTime(mWnd);
6230 return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
6233 void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
6234 // Retain the previous mode that was notified to observers
6235 static bool sWasSleepMode = false;
6237 // Only notify observers if mode changed
6238 if (aIsSleepMode == sWasSleepMode) return;
6240 sWasSleepMode = aIsSleepMode;
6242 nsCOMPtr<nsIObserverService> observerService =
6243 mozilla::services::GetObserverService();
6244 if (observerService)
6245 observerService->NotifyObservers(nullptr,
6246 aIsSleepMode
6247 ? NS_WIDGET_SLEEP_OBSERVER_TOPIC
6248 : NS_WIDGET_WAKE_OBSERVER_TOPIC,
6249 nullptr);
6252 LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
6253 if (IMEHandler::IsComposingOn(this)) {
6254 IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
6256 // These must be checked here too as a lone WM_CHAR could be received
6257 // if a child window didn't handle it (for example Alt+Space in a content
6258 // window)
6259 ModifierKeyState modKeyState;
6260 NativeKey nativeKey(this, aMsg, modKeyState);
6261 return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
6264 LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
6265 ModifierKeyState modKeyState;
6266 NativeKey nativeKey(this, aMsg, modKeyState);
6267 bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
6268 if (aMsg.wParam == VK_F10) {
6269 // Bug 1382199: Windows default behavior will trigger the System menu bar
6270 // when F10 is released. Among other things, this causes the System menu bar
6271 // to appear when a web page overrides the contextmenu event. We *never*
6272 // want this default behavior, so eat this key (never pass it to Windows).
6273 return true;
6275 return result;
6278 LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
6279 bool* aEventDispatched) {
6280 // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
6281 // must clean up the redirected message information itself. For more
6282 // information, see above comment of
6283 // RedirectedKeyDownMessageManager::AutoFlusher class definition in
6284 // KeyboardLayout.h.
6285 RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
6287 ModifierKeyState modKeyState;
6289 NativeKey nativeKey(this, aMsg, modKeyState);
6290 LRESULT result =
6291 static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
6292 // HandleKeyDownMessage cleaned up the redirected message information
6293 // itself, so, we should do nothing.
6294 redirectedMsgFlusher.Cancel();
6296 if (aMsg.wParam == VK_MENU ||
6297 (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
6298 // We need to let Windows handle this keypress,
6299 // by returning false, if there's a native menu
6300 // bar somewhere in our containing window hierarchy.
6301 // Otherwise we handle the keypress and don't pass
6302 // it on to Windows, by returning true.
6303 bool hasNativeMenu = false;
6304 HWND hWnd = mWnd;
6305 while (hWnd) {
6306 if (::GetMenu(hWnd)) {
6307 hasNativeMenu = true;
6308 break;
6310 hWnd = ::GetParent(hWnd);
6312 result = !hasNativeMenu;
6315 return result;
6318 nsresult nsWindow::SynthesizeNativeKeyEvent(
6319 int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
6320 uint32_t aModifierFlags, const nsAString& aCharacters,
6321 const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
6322 AutoObserverNotifier notifier(aObserver, "keyevent");
6324 KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
6325 return keyboardLayout->SynthesizeNativeKeyEvent(
6326 this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
6327 aUnmodifiedCharacters);
6330 nsresult nsWindow::SynthesizeNativeMouseEvent(
6331 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
6332 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
6333 nsIObserver* aObserver) {
6334 AutoObserverNotifier notifier(aObserver, "mouseevent");
6336 INPUT input;
6337 memset(&input, 0, sizeof(input));
6339 // TODO (bug 1693240):
6340 // Now, we synthesize native mouse events asynchronously since we want to
6341 // synthesize the event on the front window at the point. However, Windows
6342 // does not provide a way to set modifier only while a mouse message is
6343 // being handled, and MOUSEEVENTF_MOVE may be coalesced by Windows. So, we
6344 // need a trick for handling it.
6346 switch (aNativeMessage) {
6347 case NativeMouseMessage::Move:
6348 input.mi.dwFlags = MOUSEEVENTF_MOVE;
6349 // Reset sLastMouseMovePoint so that even if we're moving the mouse
6350 // to the position it's already at, we still dispatch a mousemove
6351 // event, because the callers of this function expect that.
6352 sLastMouseMovePoint = {0};
6353 break;
6354 case NativeMouseMessage::ButtonDown:
6355 case NativeMouseMessage::ButtonUp: {
6356 const bool isDown = aNativeMessage == NativeMouseMessage::ButtonDown;
6357 switch (aButton) {
6358 case MouseButton::ePrimary:
6359 input.mi.dwFlags = isDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
6360 break;
6361 case MouseButton::eMiddle:
6362 input.mi.dwFlags =
6363 isDown ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
6364 break;
6365 case MouseButton::eSecondary:
6366 input.mi.dwFlags =
6367 isDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
6368 break;
6369 case MouseButton::eX1:
6370 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6371 input.mi.mouseData = XBUTTON1;
6372 break;
6373 case MouseButton::eX2:
6374 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6375 input.mi.mouseData = XBUTTON2;
6376 break;
6377 default:
6378 return NS_ERROR_INVALID_ARG;
6380 break;
6382 case NativeMouseMessage::EnterWindow:
6383 case NativeMouseMessage::LeaveWindow:
6384 MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Windows");
6385 return NS_ERROR_INVALID_ARG;
6388 input.type = INPUT_MOUSE;
6389 ::SetCursorPos(aPoint.x, aPoint.y);
6390 ::SendInput(1, &input, sizeof(INPUT));
6392 return NS_OK;
6395 nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
6396 LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
6397 double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
6398 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
6399 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
6400 return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
6401 this, aPoint, aNativeMessage,
6402 (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
6403 ? static_cast<int32_t>(aDeltaY)
6404 : static_cast<int32_t>(aDeltaX),
6405 aModifierFlags, aAdditionalFlags);
6408 nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
6409 LayoutDeviceIntPoint aPoint,
6410 double aDeltaX, double aDeltaY,
6411 int32_t aModifierFlags,
6412 nsIObserver* aObserver) {
6413 AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
6414 DirectManipulationOwner::SynthesizeNativeTouchpadPan(
6415 this, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
6416 return NS_OK;
6419 static void MaybeLogPosChanged(HWND aWnd, WINDOWPOS* wp) {
6420 #ifdef WINSTATE_DEBUG_OUTPUT
6421 if (aWnd == WinUtils::GetTopLevelHWND(aWnd)) {
6422 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
6423 } else {
6424 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
6426 MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
6427 if (wp->flags & SWP_FRAMECHANGED) {
6428 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
6430 if (wp->flags & SWP_SHOWWINDOW) {
6431 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
6433 if (wp->flags & SWP_NOSIZE) {
6434 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
6436 if (wp->flags & SWP_HIDEWINDOW) {
6437 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
6439 if (wp->flags & SWP_NOZORDER) {
6440 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
6442 if (wp->flags & SWP_NOACTIVATE) {
6443 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
6445 MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
6446 #endif
6449 /**************************************************************
6451 * SECTION: OnXXX message handlers
6453 * For message handlers that need to be broken out or
6454 * implemented in specific platform code.
6456 **************************************************************/
6458 void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
6459 if (!wp) {
6460 return;
6463 MaybeLogPosChanged(mWnd, wp);
6465 // Handle window size mode changes
6466 if (wp->flags & SWP_FRAMECHANGED) {
6467 // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
6468 // windows when fullscreen games disable desktop composition. If we're
6469 // minimized and not being activated, ignore the event and let windows
6470 // handle it.
6471 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized &&
6472 (wp->flags & SWP_NOACTIVATE)) {
6473 return;
6476 mFrameState->OnFrameChanged();
6478 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
6479 // Skip window size change events below on minimization.
6480 return;
6484 // Notify visibility change when window is activated.
6485 if (!(wp->flags & SWP_NOACTIVATE) && NeedsToTrackWindowOcclusionState()) {
6486 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
6487 this, mFrameState->GetSizeMode() != nsSizeMode_Minimized);
6490 // Handle window position changes
6491 if (!(wp->flags & SWP_NOMOVE)) {
6492 mBounds.MoveTo(wp->x, wp->y);
6493 NotifyWindowMoved(wp->x, wp->y);
6496 // Handle window size changes
6497 if (!(wp->flags & SWP_NOSIZE)) {
6498 RECT r;
6499 int32_t newWidth, newHeight;
6501 ::GetWindowRect(mWnd, &r);
6503 newWidth = r.right - r.left;
6504 newHeight = r.bottom - r.top;
6506 if (newWidth > mLastSize.width) {
6507 RECT drect;
6509 // getting wider
6510 drect.left = wp->x + mLastSize.width;
6511 drect.top = wp->y;
6512 drect.right = drect.left + (newWidth - mLastSize.width);
6513 drect.bottom = drect.top + newHeight;
6515 ::RedrawWindow(mWnd, &drect, nullptr,
6516 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6517 RDW_ERASENOW | RDW_ALLCHILDREN);
6519 if (newHeight > mLastSize.height) {
6520 RECT drect;
6522 // getting taller
6523 drect.left = wp->x;
6524 drect.top = wp->y + mLastSize.height;
6525 drect.right = drect.left + newWidth;
6526 drect.bottom = drect.top + (newHeight - mLastSize.height);
6528 ::RedrawWindow(mWnd, &drect, nullptr,
6529 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6530 RDW_ERASENOW | RDW_ALLCHILDREN);
6533 mBounds.SizeTo(newWidth, newHeight);
6534 mLastSize.width = newWidth;
6535 mLastSize.height = newHeight;
6537 #ifdef WINSTATE_DEBUG_OUTPUT
6538 MOZ_LOG(gWindowsLog, LogLevel::Info,
6539 ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
6540 newHeight));
6541 #endif
6543 if (mAspectRatio > 0) {
6544 // It's possible (via Windows Aero Snap) that the size of the window
6545 // has changed such that it violates the aspect ratio constraint. If so,
6546 // queue up an event to enforce the aspect ratio constraint and repaint.
6547 // When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
6548 float newAspectRatio = (float)newWidth / newHeight;
6549 if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
6550 // Hold a reference to self alive and pass it into the lambda to make
6551 // sure this nsIWidget stays alive long enough to run this function.
6552 nsCOMPtr<nsIWidget> self(this);
6553 NS_DispatchToMainThread(NS_NewRunnableFunction(
6554 "EnforceAspectRatio", [self, this, newWidth]() -> void {
6555 if (mWnd) {
6556 Resize(newWidth, newWidth / mAspectRatio, true);
6558 }));
6562 // If a maximized window is resized, recalculate the non-client margins.
6563 if (mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
6564 if (UpdateNonClientMargins(true)) {
6565 // gecko resize event already sent by UpdateNonClientMargins.
6566 return;
6571 // Notify the widget listener for size change of client area for gecko
6572 // events. This needs to be done when either window size is changed,
6573 // or window frame is changed. They may not happen together.
6574 // However, we don't invoke that for popup when window frame changes,
6575 // because popups may trigger frame change before size change via
6576 // {Set,Clear}ThemeRegion they invoke in Resize. That would make the
6577 // code below call OnResize with a wrong client size first, which can
6578 // lead to flickerling for some popups.
6579 if (!(wp->flags & SWP_NOSIZE) ||
6580 ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
6581 RECT r;
6582 LayoutDeviceIntSize clientSize;
6583 if (::GetClientRect(mWnd, &r)) {
6584 clientSize = WinUtils::ToIntRect(r).Size();
6585 } else {
6586 clientSize = mBounds.Size();
6588 // Send a gecko resize event
6589 OnResize(clientSize);
6593 void nsWindow::OnWindowPosChanging(WINDOWPOS* info) {
6594 // Update non-client margins if the frame size is changing, and let the
6595 // browser know we are changing size modes, so alternative css can kick in.
6596 // If we're going into fullscreen mode, ignore this, since it'll reset
6597 // margins to normal mode.
6598 if (info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) {
6599 mFrameState->OnFrameChanging();
6602 // Force fullscreen. This works around a bug in Windows 10 1809 where
6603 // using fullscreen when a window is "snapped" causes a spurious resize
6604 // smaller than the full screen, see bug 1482920.
6605 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
6606 !(info->flags & SWP_NOMOVE) && !(info->flags & SWP_NOSIZE)) {
6607 nsCOMPtr<nsIScreenManager> screenmgr =
6608 do_GetService(sScreenManagerContractID);
6609 if (screenmgr) {
6610 LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
6611 DesktopIntRect deskBounds =
6612 RoundedToInt(bounds / GetDesktopToDeviceScale());
6613 nsCOMPtr<nsIScreen> screen;
6614 screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
6615 deskBounds.Width(), deskBounds.Height(),
6616 getter_AddRefs(screen));
6618 if (screen) {
6619 auto rect = screen->GetRect();
6620 info->x = rect.x;
6621 info->y = rect.y;
6622 info->cx = rect.width;
6623 info->cy = rect.height;
6628 // enforce local z-order rules
6629 if (!(info->flags & SWP_NOZORDER)) {
6630 HWND hwndAfter = info->hwndInsertAfter;
6632 nsWindow* aboveWindow = 0;
6633 nsWindowZ placement;
6635 if (hwndAfter == HWND_BOTTOM)
6636 placement = nsWindowZBottom;
6637 else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST ||
6638 hwndAfter == HWND_NOTOPMOST)
6639 placement = nsWindowZTop;
6640 else {
6641 placement = nsWindowZRelative;
6642 aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter);
6645 if (mWidgetListener) {
6646 nsCOMPtr<nsIWidget> actualBelow = nullptr;
6647 if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow,
6648 getter_AddRefs(actualBelow))) {
6649 if (placement == nsWindowZBottom)
6650 info->hwndInsertAfter = HWND_BOTTOM;
6651 else if (placement == nsWindowZTop)
6652 info->hwndInsertAfter = HWND_TOP;
6653 else {
6654 info->hwndInsertAfter =
6655 (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW);
6660 // prevent rude external programs from making hidden window visible
6661 if (mWindowType == WindowType::Invisible) info->flags &= ~SWP_SHOWWINDOW;
6663 // When waking from sleep or switching out of tablet mode, Windows 10
6664 // Version 1809 will reopen popup windows that should be hidden. Detect
6665 // this case and refuse to show the window.
6666 static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
6667 if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
6668 mWindowType == WindowType::Popup && mWidgetListener &&
6669 mWidgetListener->ShouldNotBeVisible()) {
6670 info->flags &= ~SWP_SHOWWINDOW;
6674 void nsWindow::UserActivity() {
6675 // Check if we have the idle service, if not we try to get it.
6676 if (!mIdleService) {
6677 mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
6680 // Check that we now have the idle service.
6681 if (mIdleService) {
6682 mIdleService->ResetIdleTimeOut(0);
6686 // Helper function for TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT,
6687 // uint32_t).
6688 static bool TouchDeviceNeedsPanGestureConversion(HANDLE aSource) {
6689 std::string deviceName;
6690 UINT dataSize = 0;
6691 // The first call just queries how long the name string will be.
6692 GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, nullptr, &dataSize);
6693 if (!dataSize || dataSize > 0x10000) {
6694 return false;
6696 deviceName.resize(dataSize);
6697 // The second call actually populates the string.
6698 UINT result = GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, &deviceName[0],
6699 &dataSize);
6700 if (result == UINT_MAX) {
6701 return false;
6703 // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
6704 // needs to be escaped with another one.
6705 std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
6706 // For some reason, the dataSize returned by the first call is double the
6707 // actual length of the device name (as if it were returning the size of a
6708 // wide-character string in bytes) even though we are using the narrow
6709 // version of the API. For the comparison against the expected device name
6710 // to pass, we truncate the buffer to be no longer tha the expected device
6711 // name.
6712 if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
6713 return false;
6716 RID_DEVICE_INFO deviceInfo;
6717 deviceInfo.cbSize = sizeof(deviceInfo);
6718 dataSize = sizeof(deviceInfo);
6719 result =
6720 GetRawInputDeviceInfoA(aSource, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
6721 if (result == UINT_MAX) {
6722 return false;
6724 // The device identifiers that we check for here come from bug 1355162
6725 // comment 1 (see also bug 1511901 comment 35).
6726 return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
6727 deviceInfo.hid.dwProductId == 0 &&
6728 deviceInfo.hid.dwVersionNumber == 1 &&
6729 deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
6732 // Determine if the touch device that originated |aOSEvent| needs to have
6733 // touch events representing a two-finger gesture converted to pan
6734 // gesture events.
6735 // We only do this for touch devices with a specific name and identifiers.
6736 static bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
6737 uint32_t aTouchCount) {
6738 if (!StaticPrefs::apz_windows_check_for_pan_gesture_conversion()) {
6739 return false;
6741 if (aTouchCount == 0) {
6742 return false;
6744 HANDLE source = aOSEvent[0].hSource;
6746 // Cache the result of this computation for each touch device.
6747 // Touch devices are identified by the HANDLE stored in the hSource
6748 // field of TOUCHINPUT.
6749 static std::map<HANDLE, bool> sResultCache;
6750 auto [iter, inserted] = sResultCache.emplace(source, false);
6751 if (inserted) {
6752 iter->second = TouchDeviceNeedsPanGestureConversion(source);
6754 return iter->second;
6757 Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
6758 const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
6759 // Checks if the touch device that originated the touch event is one
6760 // for which we want to convert the touch events to pang gesture events.
6761 bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
6762 aOSEvent, aTouchInput.mTouches.Length());
6763 if (!shouldConvert) {
6764 return Nothing();
6767 // Only two-finger gestures need conversion.
6768 if (aTouchInput.mTouches.Length() != 2) {
6769 return Nothing();
6772 PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
6773 if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
6774 eventType = PanGestureInput::PANGESTURE_START;
6775 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
6776 eventType = PanGestureInput::PANGESTURE_END;
6777 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
6778 eventType = PanGestureInput::PANGESTURE_CANCELLED;
6781 // Use the midpoint of the two touches as the start point of the pan gesture.
6782 ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
6783 aTouchInput.mTouches[1].mScreenPoint) /
6785 // To compute the displacement of the pan gesture, we keep track of the
6786 // location of the previous event.
6787 ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
6788 ? ScreenPoint(0, 0)
6789 : (focusPoint - mLastPanGestureFocus);
6790 mLastPanGestureFocus = focusPoint;
6792 // We need to negate the displacement because for a touch event, moving the
6793 // fingers down results in scrolling up, but for a touchpad gesture, we want
6794 // moving the fingers down to result in scrolling down.
6795 PanGestureInput result(eventType, aTouchInput.mTimeStamp, focusPoint,
6796 -displacement, aTouchInput.modifiers);
6797 result.mSimulateMomentum = true;
6799 return Some(result);
6802 // Dispatch an event that originated as an OS touch event.
6803 // Usually, we want to dispatch it as a touch event, but some touchpads
6804 // produce touch events for two-finger scrolling, which need to be converted
6805 // to pan gesture events for correct behaviour.
6806 void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
6807 PTOUCHINPUT aOSEvent) {
6808 if (Maybe<PanGestureInput> panInput =
6809 ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
6810 DispatchPanGestureInput(*panInput);
6811 return;
6814 DispatchTouchInput(aTouchInput);
6817 bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
6818 uint32_t cInputs = LOWORD(wParam);
6819 PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
6821 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
6822 sizeof(TOUCHINPUT))) {
6823 MultiTouchInput touchInput, touchEndInput;
6825 // Walk across the touch point array processing each contact point.
6826 for (uint32_t i = 0; i < cInputs; i++) {
6827 bool addToEvent = false, addToEndEvent = false;
6829 // N.B.: According with MS documentation
6830 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
6831 // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
6832 // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
6833 // TOUCHEVENTF_UP can be combined together.
6835 if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
6836 if (touchInput.mTimeStamp.IsNull()) {
6837 // Initialize a touch event to send.
6838 touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
6839 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6840 ModifierKeyState modifierKeyState;
6841 touchInput.modifiers = modifierKeyState.GetModifiers();
6843 // Pres shell expects this event to be a eTouchStart
6844 // if any new contact points have been added since the last event sent.
6845 if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
6846 touchInput.mType = MultiTouchInput::MULTITOUCH_START;
6848 addToEvent = true;
6850 if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
6851 // Pres shell expects removed contacts points to be delivered in a
6852 // separate eTouchEnd event containing only the contact points that were
6853 // removed.
6854 if (touchEndInput.mTimeStamp.IsNull()) {
6855 // Initialize a touch event to send.
6856 touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
6857 touchEndInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6858 ModifierKeyState modifierKeyState;
6859 touchEndInput.modifiers = modifierKeyState.GetModifiers();
6861 addToEndEvent = true;
6863 if (!addToEvent && !addToEndEvent) {
6864 // Filter out spurious Windows events we don't understand, like palm
6865 // contact.
6866 continue;
6869 // Setup the touch point we'll append to the touch event array.
6870 nsPointWin touchPoint;
6871 touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
6872 touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
6873 touchPoint.ScreenToClient(mWnd);
6875 // Initialize the touch data.
6876 SingleTouchData touchData(
6877 pInputs[i].dwID, // aIdentifier
6878 ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
6879 // The contact area info cannot be trusted even when
6880 // TOUCHINPUTMASKF_CONTACTAREA is set when the input source is pen,
6881 // which somehow violates the API docs. (bug 1710509) Ultimately the
6882 // dwFlags check will become redundant since we want to migrate to
6883 // WM_POINTER for pens. (bug 1707075)
6884 (pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA) &&
6885 !(pInputs[i].dwFlags & TOUCHEVENTF_PEN)
6886 ? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
6887 TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
6888 : ScreenSize(1, 1), // aRadius
6889 0.0f, // aRotationAngle
6890 0.0f); // aForce
6892 // Append touch data to the appropriate event.
6893 if (addToEvent) {
6894 touchInput.mTouches.AppendElement(touchData);
6896 if (addToEndEvent) {
6897 touchEndInput.mTouches.AppendElement(touchData);
6901 // Dispatch touch start and touch move event if we have one.
6902 if (!touchInput.mTimeStamp.IsNull()) {
6903 DispatchTouchOrPanGestureInput(touchInput, pInputs);
6905 // Dispatch touch end event if we have one.
6906 if (!touchEndInput.mTimeStamp.IsNull()) {
6907 DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
6911 delete[] pInputs;
6912 CloseTouchInputHandle((HTOUCHINPUT)lParam);
6913 return true;
6916 // Gesture event processing. Handles WM_GESTURE events.
6917 bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
6918 // Treatment for pan events which translate into scroll events:
6919 if (mGesture.IsPanEvent(lParam)) {
6920 if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
6921 return false; // ignore
6923 nsEventStatus status;
6925 WidgetWheelEvent wheelEvent(true, eWheel, this);
6927 ModifierKeyState modifierKeyState;
6928 modifierKeyState.InitInputEvent(wheelEvent);
6930 wheelEvent.mButton = 0;
6931 wheelEvent.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6932 wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
6934 bool endFeedback = true;
6936 if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
6937 DispatchEvent(&wheelEvent, status);
6940 if (mDisplayPanFeedback) {
6941 mGesture.UpdatePanFeedbackX(
6942 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
6943 endFeedback);
6944 mGesture.UpdatePanFeedbackY(
6945 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
6946 endFeedback);
6947 mGesture.PanFeedbackFinalize(mWnd, endFeedback);
6950 CloseGestureInfoHandle((HGESTUREINFO)lParam);
6952 return true;
6955 // Other gestures translate into simple gesture events:
6956 WidgetSimpleGestureEvent event(true, eVoidEvent, this);
6957 if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
6958 return false; // fall through to DefWndProc
6961 // Polish up and send off the new event
6962 ModifierKeyState modifierKeyState;
6963 modifierKeyState.InitInputEvent(event);
6964 event.mButton = 0;
6965 event.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6966 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
6968 nsEventStatus status;
6969 DispatchEvent(&event, status);
6970 if (status == nsEventStatus_eIgnore) {
6971 return false; // Ignored, fall through
6974 // Only close this if we process and return true.
6975 CloseGestureInfoHandle((HGESTUREINFO)lParam);
6977 return true; // Handled
6980 // WM_DESTROY event handler
6981 void nsWindow::OnDestroy() {
6982 mOnDestroyCalled = true;
6984 // If this is a toplevel window, notify the taskbar concealer to clean up any
6985 // relevant state.
6986 if (!mParent) {
6987 TaskbarConcealer::OnWindowDestroyed(mWnd);
6990 // Make sure we don't get destroyed in the process of tearing down.
6991 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
6993 // Dispatch the destroy notification.
6994 if (!mInDtor) NotifyWindowDestroyed();
6996 // Prevent the widget from sending additional events.
6997 mWidgetListener = nullptr;
6998 mAttachedWidgetListener = nullptr;
7000 DestroyDirectManipulation();
7002 if (mWnd == mLastKillFocusWindow) {
7003 mLastKillFocusWindow = nullptr;
7005 // Unregister notifications from terminal services
7006 ::WTSUnRegisterSessionNotification(mWnd);
7008 // We will stop receiving native events after dissociating from our native
7009 // window. We will also disappear from the output of WinUtils::GetNSWindowPtr
7010 // for that window.
7011 DissociateFromNativeWindow();
7013 // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
7014 // can be cleared. (It's used in tracking windows for mouse events.)
7015 if (sCurrentWindow == this) sCurrentWindow = nullptr;
7017 // Disconnects us from our parent, will call our GetParent().
7018 nsBaseWidget::Destroy();
7020 // Release references to children, device context, toolkit, and app shell.
7021 nsBaseWidget::OnDestroy();
7023 // Clear our native parent handle.
7024 // XXX Windows will take care of this in the proper order, and
7025 // SetParent(nullptr)'s remove child on the parent already took place in
7026 // nsBaseWidget's Destroy call above.
7027 // SetParent(nullptr);
7028 mParent = nullptr;
7030 // We have to destroy the native drag target before we null out our window
7031 // pointer.
7032 EnableDragDrop(false);
7034 // If we're going away and for some reason we're still the rollup widget,
7035 // rollup and turn off capture.
7036 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7037 nsCOMPtr<nsIWidget> rollupWidget;
7038 if (rollupListener) {
7039 rollupWidget = rollupListener->GetRollupWidget();
7041 if (this == rollupWidget) {
7042 rollupListener->Rollup({});
7043 CaptureRollupEvents(false);
7046 IMEHandler::OnDestroyWindow(this);
7048 // Free GDI window class objects
7049 if (mBrush) {
7050 VERIFY(::DeleteObject(mBrush));
7051 mBrush = nullptr;
7054 // Destroy any custom cursor resources.
7055 if (mCursor.IsCustom()) {
7056 SetCursor(Cursor{eCursor_standard});
7059 if (mCompositorWidgetDelegate) {
7060 mCompositorWidgetDelegate->OnDestroyWindow();
7062 mBasicLayersSurface = nullptr;
7064 // Finalize panning feedback to possibly restore window displacement
7065 mGesture.PanFeedbackFinalize(mWnd, true);
7067 // Clear the main HWND.
7068 mWnd = nullptr;
7071 // Send a resize message to the listener
7072 bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
7073 if (mCompositorWidgetDelegate &&
7074 !mCompositorWidgetDelegate->OnWindowResize(aSize)) {
7075 return false;
7078 bool result = false;
7079 if (mWidgetListener) {
7080 result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
7083 // If there is an attached view, inform it as well as the normal widget
7084 // listener.
7085 if (mAttachedWidgetListener) {
7086 return mAttachedWidgetListener->WindowResized(this, aSize.width,
7087 aSize.height);
7090 return result;
7093 void nsWindow::OnSizeModeChange() {
7094 const nsSizeMode mode = mFrameState->GetSizeMode();
7096 MOZ_LOG(gWindowsLog, LogLevel::Info,
7097 ("nsWindow::OnSizeModeChange() sizeMode %d", mode));
7099 if (NeedsToTrackWindowOcclusionState()) {
7100 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
7101 this, mode != nsSizeMode_Minimized);
7103 wr::DebugFlags flags{0};
7104 flags._0 = gfx::gfxVars::WebRenderDebugFlags();
7105 bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
7106 if (debugEnabled && mCompositorWidgetDelegate) {
7107 mCompositorWidgetDelegate->NotifyVisibilityUpdated(mode,
7108 mIsFullyOccluded);
7112 if (mCompositorWidgetDelegate) {
7113 mCompositorWidgetDelegate->OnWindowModeChange(mode);
7116 if (mWidgetListener) {
7117 mWidgetListener->SizeModeChanged(mode);
7121 bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
7123 bool nsWindow::IsPopup() { return mWindowType == WindowType::Popup; }
7125 bool nsWindow::ShouldUseOffMainThreadCompositing() {
7126 if (mWindowType == WindowType::Popup && mPopupType == PopupType::Tooltip) {
7127 return false;
7130 // Content rendering of popup is always done by child window.
7131 // See nsDocumentViewer::ShouldAttachToTopLevel().
7132 if (mWindowType == WindowType::Popup && !mIsChildWindow) {
7133 MOZ_ASSERT(!mParent);
7134 return false;
7137 return nsBaseWidget::ShouldUseOffMainThreadCompositing();
7140 void nsWindow::WindowUsesOMTC() {
7141 ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
7142 if (!style) {
7143 NS_WARNING("Could not get window class style");
7144 return;
7146 style |= CS_HREDRAW | CS_VREDRAW;
7147 DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
7148 NS_WARNING_ASSERTION(result, "Could not reset window class style");
7151 void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
7152 int32_t height) {
7153 // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
7154 // they remain tied to their original parent's resolution.
7155 if (mWindowType == WindowType::Popup) {
7156 return;
7158 if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
7159 return;
7161 mDefaultScale = -1.0; // force recomputation of scale factor
7163 if (mResizeState != RESIZING &&
7164 mFrameState->GetSizeMode() == nsSizeMode_Normal) {
7165 // Limit the position (if not in the middle of a drag-move) & size,
7166 // if it would overflow the destination screen
7167 nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID);
7168 if (sm) {
7169 nsCOMPtr<nsIScreen> screen;
7170 sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
7171 if (screen) {
7172 int32_t availLeft, availTop, availWidth, availHeight;
7173 screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
7174 if (mResizeState != MOVING) {
7175 x = std::max(x, availLeft);
7176 y = std::max(y, availTop);
7178 width = std::min(width, availWidth);
7179 height = std::min(height, availHeight);
7183 Resize(x, y, width, height, true);
7185 UpdateNonClientMargins();
7186 ChangedDPI();
7187 ResetLayout();
7190 // Callback to generate OnCloakChanged pseudo-events.
7191 /* static */
7192 void nsWindow::OnCloakEvent(HWND aWnd, bool aCloaked) {
7193 MOZ_ASSERT(NS_IsMainThread());
7195 const char* const kEventName = aCloaked ? "CLOAKED" : "UNCLOAKED";
7196 nsWindow* pWin = WinUtils::GetNSWindowPtr(aWnd);
7197 if (!pWin) {
7198 MOZ_LOG(
7199 sCloakingLog, LogLevel::Debug,
7200 ("Received %s event for HWND %p (not an nsWindow)", kEventName, aWnd));
7201 return;
7204 const char* const kWasCloakedStr = pWin->mIsCloaked ? "cloaked" : "uncloaked";
7205 if (mozilla::IsCloaked(aWnd) == pWin->mIsCloaked) {
7206 MOZ_LOG(sCloakingLog, LogLevel::Debug,
7207 ("Received redundant %s event for %s HWND %p; discarding",
7208 kEventName, kWasCloakedStr, aWnd));
7209 return;
7212 MOZ_LOG(
7213 sCloakingLog, LogLevel::Info,
7214 ("Received %s event for %s HWND %p", kEventName, kWasCloakedStr, aWnd));
7216 // Cloaking events like the one we've just received are sent asynchronously.
7217 // Rather than process them one-by-one, we jump the gun a bit and perform
7218 // updates on all newly cloaked/uncloaked nsWindows at once. This also lets us
7219 // batch operations that consider more than one window's state.
7220 struct Item {
7221 nsWindow* win;
7222 bool nowCloaked;
7224 nsTArray<Item> changedWindows;
7226 mozilla::EnumerateThreadWindows([&](HWND hwnd) {
7227 nsWindow* pWin = WinUtils::GetNSWindowPtr(hwnd);
7228 if (!pWin) {
7229 return;
7232 const bool isCloaked = mozilla::IsCloaked(hwnd);
7233 if (isCloaked != pWin->mIsCloaked) {
7234 changedWindows.AppendElement(Item{pWin, isCloaked});
7238 if (changedWindows.IsEmpty()) {
7239 return;
7242 for (const Item& item : changedWindows) {
7243 item.win->OnCloakChanged(item.nowCloaked);
7246 nsWindow::TaskbarConcealer::OnCloakChanged();
7249 void nsWindow::OnCloakChanged(bool aCloaked) {
7250 MOZ_LOG(sCloakingLog, LogLevel::Info,
7251 ("Calling OnCloakChanged(): HWND %p, aCloaked %s", mWnd,
7252 aCloaked ? "true" : "false"));
7253 mIsCloaked = aCloaked;
7256 /**************************************************************
7257 **************************************************************
7259 ** BLOCK: IME management and accessibility
7261 ** Handles managing IME input and accessibility.
7263 **************************************************************
7264 **************************************************************/
7266 void nsWindow::SetInputContext(const InputContext& aContext,
7267 const InputContextAction& aAction) {
7268 InputContext newInputContext = aContext;
7269 IMEHandler::SetInputContext(this, newInputContext, aAction);
7270 mInputContext = newInputContext;
7273 InputContext nsWindow::GetInputContext() {
7274 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7275 if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
7276 mInputContext.mIMEState.mOpen = IMEState::OPEN;
7277 } else {
7278 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7280 return mInputContext;
7283 TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
7284 return IMEHandler::GetNativeTextEventDispatcherListener();
7287 #ifdef ACCESSIBILITY
7288 # ifdef DEBUG
7289 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
7290 if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
7291 printf( \
7292 "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
7293 "%p,\n", \
7294 aHwnd, ::GetParent(aHwnd), aWnd); \
7295 printf(" acc: %p", aAcc); \
7296 if (aAcc) { \
7297 nsAutoString name; \
7298 aAcc->Name(name); \
7299 printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
7301 printf("\n }\n"); \
7304 # else
7305 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
7306 # endif
7308 a11y::LocalAccessible* nsWindow::GetAccessible() {
7309 // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
7310 if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
7311 return nullptr;
7313 if (mInDtor || mOnDestroyCalled || mWindowType == WindowType::Invisible) {
7314 return nullptr;
7317 // In case of popup window return a popup accessible.
7318 nsView* view = nsView::GetViewFor(this);
7319 if (view) {
7320 nsIFrame* frame = view->GetFrame();
7321 if (frame && nsLayoutUtils::IsPopup(frame)) {
7322 nsAccessibilityService* accService = GetOrCreateAccService();
7323 if (accService) {
7324 a11y::DocAccessible* docAcc =
7325 GetAccService()->GetDocAccessible(frame->PresShell());
7326 if (docAcc) {
7327 NS_LOG_WMGETOBJECT(
7328 this, mWnd,
7329 docAcc->GetAccessibleOrDescendant(frame->GetContent()));
7330 return docAcc->GetAccessibleOrDescendant(frame->GetContent());
7336 // otherwise root document accessible.
7337 NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
7338 return GetRootAccessible();
7340 #endif
7342 /**************************************************************
7343 **************************************************************
7345 ** BLOCK: Transparency
7347 ** Window transparency helpers.
7349 **************************************************************
7350 **************************************************************/
7352 void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) {
7353 if (aMode == mTransparencyMode) return;
7355 // stop on dialogs and popups!
7356 HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
7357 nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd);
7359 if (!parent) {
7360 NS_WARNING("Trying to use transparent chrome in an embedded context");
7361 return;
7364 if (parent != this) {
7365 NS_WARNING(
7366 "Setting SetWindowTranslucencyInner on a parent this is not us!");
7369 if (aMode == TransparencyMode::Transparent) {
7370 // If we're switching to the use of a transparent window, hide the chrome
7371 // on our parent.
7372 HideWindowChrome(true);
7373 } else if (mHideChrome &&
7374 mTransparencyMode == TransparencyMode::Transparent) {
7375 // if we're switching out of transparent, re-enable our parent's chrome.
7376 HideWindowChrome(false);
7379 LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE),
7380 exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
7382 if (parent->mIsVisible) {
7383 style |= WS_VISIBLE;
7384 if (parent->mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
7385 style |= WS_MAXIMIZE;
7386 } else if (parent->mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
7387 style |= WS_MINIMIZE;
7391 if (aMode == TransparencyMode::Transparent)
7392 exStyle |= WS_EX_LAYERED;
7393 else
7394 exStyle &= ~WS_EX_LAYERED;
7396 VERIFY_WINDOW_STYLE(style);
7397 ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
7398 ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
7400 mTransparencyMode = aMode;
7402 if (mCompositorWidgetDelegate) {
7403 mCompositorWidgetDelegate->UpdateTransparency(aMode);
7407 /**************************************************************
7408 **************************************************************
7410 ** BLOCK: Popup rollup hooks
7412 ** Deals with CaptureRollup on popup windows.
7414 **************************************************************
7415 **************************************************************/
7417 // Schedules a timer for a window, so we can rollup after processing the hook
7418 // event
7419 void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
7420 // In some cases multiple hooks may be scheduled
7421 // so ignore any other requests once one timer is scheduled
7422 if (sHookTimerId == 0) {
7423 // Remember the window handle and the message ID to be used later
7424 sRollupMsgId = aMsgId;
7425 sRollupMsgWnd = aWnd;
7426 // Schedule native timer for doing the rollup after
7427 // this event is done being processed
7428 sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
7429 NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
7433 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7434 int gLastMsgCode = 0;
7435 extern MSGFEventMsgInfo gMSGFEvents[];
7436 #endif
7438 // Process Menu messages, rollup when popup is clicked.
7439 LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
7440 LPARAM lParam) {
7441 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7442 if (sProcessHook) {
7443 MSG* pMsg = (MSG*)lParam;
7445 int inx = 0;
7446 while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
7447 inx++;
7449 if (code != gLastMsgCode) {
7450 if (gMSGFEvents[inx].mId == code) {
7451 # ifdef DEBUG
7452 MOZ_LOG(gWindowsLog, LogLevel::Info,
7453 ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
7454 gMSGFEvents[inx].mStr, pMsg->hwnd));
7455 # endif
7456 } else {
7457 # ifdef DEBUG
7458 MOZ_LOG(gWindowsLog, LogLevel::Info,
7459 ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
7460 gMSGFEvents[inx].mId, pMsg->hwnd));
7461 # endif
7463 gLastMsgCode = code;
7465 PrintEvent(pMsg->message, FALSE, FALSE);
7467 #endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7469 if (sProcessHook && code == MSGF_MENU) {
7470 MSG* pMsg = (MSG*)lParam;
7471 ScheduleHookTimer(pMsg->hwnd, pMsg->message);
7474 return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
7477 // Process all mouse messages. Roll up when a click is in a native window
7478 // that doesn't have an nsIWidget.
7479 LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
7480 LPARAM lParam) {
7481 if (sProcessHook) {
7482 switch (WinUtils::GetNativeMessage(wParam)) {
7483 case WM_LBUTTONDOWN:
7484 case WM_RBUTTONDOWN:
7485 case WM_MBUTTONDOWN:
7486 case WM_MOUSEWHEEL:
7487 case WM_MOUSEHWHEEL: {
7488 MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
7489 nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
7490 if (!mozWin) {
7491 ScheduleHookTimer(ms->hwnd, (UINT)wParam);
7493 break;
7497 return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
7500 // Process all messages. Roll up when the window is moving, or
7501 // is resizing or when maximized or mininized.
7502 LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
7503 LPARAM lParam) {
7504 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7505 if (sProcessHook) {
7506 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7507 PrintEvent(cwpt->message, FALSE, FALSE);
7509 #endif
7511 if (sProcessHook) {
7512 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7513 if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
7514 cwpt->message == WM_GETMINMAXINFO) {
7515 ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
7519 return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
7522 // Register the special "hooks" for dropdown processing.
7523 void nsWindow::RegisterSpecialDropdownHooks() {
7524 NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
7525 NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
7527 DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
7529 // Install msg hook for moving the window and resizing
7530 if (!sMsgFilterHook) {
7531 DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
7532 sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
7533 nullptr, GetCurrentThreadId());
7534 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7535 if (!sMsgFilterHook) {
7536 MOZ_LOG(gWindowsLog, LogLevel::Info,
7537 ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
7539 #endif
7542 // Install msg hook for menus
7543 if (!sCallProcHook) {
7544 DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
7545 sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
7546 GetCurrentThreadId());
7547 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7548 if (!sCallProcHook) {
7549 MOZ_LOG(
7550 gWindowsLog, LogLevel::Info,
7551 ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
7553 #endif
7556 // Install msg hook for the mouse
7557 if (!sCallMouseHook) {
7558 DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
7559 sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
7560 GetCurrentThreadId());
7561 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7562 if (!sCallMouseHook) {
7563 MOZ_LOG(gWindowsLog, LogLevel::Info,
7564 ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
7566 #endif
7570 // Unhook special message hooks for dropdowns.
7571 void nsWindow::UnregisterSpecialDropdownHooks() {
7572 DISPLAY_NMM_PRT(
7573 "***************** De-installing Msg Hooks ***************\n");
7575 if (sCallProcHook) {
7576 DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
7577 if (!::UnhookWindowsHookEx(sCallProcHook)) {
7578 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
7580 sCallProcHook = nullptr;
7583 if (sMsgFilterHook) {
7584 DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
7585 if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
7586 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
7588 sMsgFilterHook = nullptr;
7591 if (sCallMouseHook) {
7592 DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
7593 if (!::UnhookWindowsHookEx(sCallMouseHook)) {
7594 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
7596 sCallMouseHook = nullptr;
7600 // This timer is designed to only fire one time at most each time a "hook"
7601 // function is used to rollup the dropdown. In some cases, the timer may be
7602 // scheduled from the hook, but that hook event or a subsequent event may roll
7603 // up the dropdown before this timer function is executed.
7605 // For example, if an MFC control takes focus, the combobox will lose focus and
7606 // rollup before this function fires.
7607 VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
7608 DWORD dwTime) {
7609 if (sHookTimerId != 0) {
7610 // if the window is nullptr then we need to use the ID to kill the timer
7611 DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
7612 NS_ASSERTION(status, "Hook Timer was not killed.");
7613 sHookTimerId = 0;
7616 if (sRollupMsgId != 0) {
7617 // Note: DealWithPopups does the check to make sure that the rollup widget
7618 // is set.
7619 LRESULT popupHandlingResult;
7620 nsAutoRollup autoRollup;
7621 DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
7622 sRollupMsgId = 0;
7623 sRollupMsgWnd = nullptr;
7627 static bool IsDifferentThreadWindow(HWND aWnd) {
7628 return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
7631 // static
7632 bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
7633 Maybe<POINT> aEventPoint) {
7634 RECT r;
7635 ::GetWindowRect(aWindow->mWnd, &r);
7636 POINT mp;
7637 if (aEventPoint) {
7638 mp = *aEventPoint;
7639 } else {
7640 DWORD pos = ::GetMessagePos();
7641 mp.x = GET_X_LPARAM(pos);
7642 mp.y = GET_Y_LPARAM(pos);
7645 auto margin = aWindow->mInputRegion.mMargin;
7646 if (margin > 0) {
7647 r.top += margin;
7648 r.bottom -= margin;
7649 r.left += margin;
7650 r.right -= margin;
7653 // was the event inside this window?
7654 return static_cast<bool>(::PtInRect(&r, mp));
7657 // static
7658 bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
7659 uint32_t* aPopupsToRollup,
7660 Maybe<POINT> aEventPoint) {
7661 // If we're dealing with menus, we probably have submenus and we don't want
7662 // to rollup some of them if the click is in a parent menu of the current
7663 // submenu.
7664 *aPopupsToRollup = UINT32_MAX;
7665 AutoTArray<nsIWidget*, 5> widgetChain;
7666 uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
7667 for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
7668 nsIWidget* widget = widgetChain[i];
7669 if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
7670 // Don't roll up if the mouse event occurred within a menu of the
7671 // same type. If the mouse event occurred in a menu higher than that,
7672 // roll up, but pass the number of popups to Rollup so that only those
7673 // of the same type close up.
7674 if (i < sameTypeCount) {
7675 return false;
7678 *aPopupsToRollup = sameTypeCount;
7679 break;
7682 return true;
7685 // static
7686 bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) {
7687 // While popup is open, popup window might be activated by other application.
7688 // At this time, we need to take back focus to the previous window but it
7689 // causes flickering its nonclient area because WM_NCACTIVATE comes before
7690 // WM_ACTIVATE and we cannot know which window will take focus at receiving
7691 // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
7693 // If non-popup window receives WM_NCACTIVATE at deactivating, default
7694 // wndproc shouldn't handle it as deactivating. Instead, at receiving
7695 // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
7696 // This returns true if the window needs to handle WM_NCACTIVATE later.
7698 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7699 return window && !window->IsPopup();
7702 static bool IsTouchSupportEnabled(HWND aWnd) {
7703 nsWindow* topWindow =
7704 WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
7705 return topWindow ? topWindow->IsTouchWindow() : false;
7708 static Maybe<POINT> GetSingleTouch(WPARAM wParam, LPARAM lParam) {
7709 Maybe<POINT> ret;
7710 uint32_t cInputs = LOWORD(wParam);
7711 if (cInputs != 1) {
7712 return ret;
7714 TOUCHINPUT input;
7715 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, &input,
7716 sizeof(TOUCHINPUT))) {
7717 ret.emplace();
7718 ret->x = TOUCH_COORD_TO_PIXEL(input.x);
7719 ret->y = TOUCH_COORD_TO_PIXEL(input.y);
7721 // Note that we don't call CloseTouchInputHandle here because we need
7722 // to read the touch input info again in OnTouch later.
7723 return ret;
7726 // static
7727 bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
7728 LPARAM aLParam, LRESULT* aResult) {
7729 NS_ASSERTION(aResult, "Bad outResult");
7731 // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
7732 *aResult = MA_NOACTIVATE;
7734 if (!::IsWindowVisible(aWnd)) {
7735 return false;
7738 if (MOZ_UNLIKELY(aMessage == WM_KILLFOCUS)) {
7739 // NOTE: We deal with this here rather than on the switch below because we
7740 // want to do this even if there are no menus to rollup (tooltips don't set
7741 // the rollup listener etc).
7742 if (RefPtr pm = nsXULPopupManager::GetInstance()) {
7743 pm->RollupTooltips();
7747 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7748 NS_ENSURE_TRUE(rollupListener, false);
7750 nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
7751 if (!popup) {
7752 return false;
7755 static bool sSendingNCACTIVATE = false;
7756 static bool sPendingNCACTIVATE = false;
7757 uint32_t popupsToRollup = UINT32_MAX;
7759 bool consumeRollupEvent = false;
7760 Maybe<POINT> touchPoint; // In screen coords.
7762 // If we rollup with animations but get occluded right away, we might not
7763 // advance the refresh driver enough for the animation to finish.
7764 auto allowAnimations = nsIRollupListener::AllowAnimations::Yes;
7765 nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
7766 UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
7767 switch (nativeMessage) {
7768 case WM_TOUCH:
7769 if (!IsTouchSupportEnabled(aWnd)) {
7770 // If APZ is disabled, don't allow touch inputs to dismiss popups. The
7771 // compatibility mouse events will do it instead.
7772 return false;
7774 touchPoint = GetSingleTouch(aWParam, aLParam);
7775 if (!touchPoint) {
7776 return false;
7778 [[fallthrough]];
7779 case WM_LBUTTONDOWN:
7780 case WM_RBUTTONDOWN:
7781 case WM_MBUTTONDOWN:
7782 case WM_NCLBUTTONDOWN:
7783 case WM_NCRBUTTONDOWN:
7784 case WM_NCMBUTTONDOWN:
7785 if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
7786 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
7787 // If any of these mouse events are really compatibility events that
7788 // Windows is sending for touch inputs, then don't allow them to dismiss
7789 // popups when APZ is enabled (instead we do the dismissing as part of
7790 // WM_TOUCH handling which is more correct).
7791 // If we don't do this, then when the user lifts their finger after a
7792 // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
7793 // us will dismiss the contextmenu popup that we displayed as part of
7794 // handling the long-tap-up.
7795 return false;
7797 if (!EventIsInsideWindow(popupWindow, touchPoint) &&
7798 GetPopupsToRollup(rollupListener, &popupsToRollup, touchPoint)) {
7799 break;
7801 return false;
7802 case WM_POINTERDOWN: {
7803 WinPointerEvents pointerEvents;
7804 if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) {
7805 return false;
7807 POINT pt;
7808 pt.x = GET_X_LPARAM(aLParam);
7809 pt.y = GET_Y_LPARAM(aLParam);
7810 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
7811 return false;
7813 if (EventIsInsideWindow(popupWindow, Some(pt))) {
7814 // Don't roll up if the event is inside the popup window.
7815 return false;
7817 } break;
7818 case MOZ_WM_DMANIP: {
7819 POINT pt;
7820 ::GetCursorPos(&pt);
7821 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
7822 return false;
7824 if (EventIsInsideWindow(popupWindow, Some(pt))) {
7825 // Don't roll up if the event is inside the popup window
7826 return false;
7828 } break;
7829 case WM_MOUSEWHEEL:
7830 case WM_MOUSEHWHEEL:
7831 // We need to check if the popup thinks that it should cause closing
7832 // itself when mouse wheel events are fired outside the rollup widget.
7833 if (!EventIsInsideWindow(popupWindow)) {
7834 // Check if we should consume this event even if we don't roll-up:
7835 consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
7836 *aResult = MA_ACTIVATE;
7837 if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
7838 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7839 break;
7842 return consumeRollupEvent;
7844 case WM_ACTIVATEAPP:
7845 allowAnimations = nsIRollupListener::AllowAnimations::No;
7846 break;
7848 case WM_ACTIVATE: {
7849 WndProcUrgentInvocation::Marker _marker;
7851 // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
7852 // because we cannot distinguish it's caused by mouse or not.
7853 if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
7854 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7855 if (window && window->IsPopup()) {
7856 // Cancel notifying widget listeners of deactivating the previous
7857 // active window (see WM_KILLFOCUS case in ProcessMessage()).
7858 sJustGotDeactivate = false;
7859 // Reactivate the window later.
7860 ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
7861 return true;
7863 // Don't rollup the popup when focus moves back to the parent window
7864 // from a popup because such case is caused by strange mouse drivers.
7865 nsWindow* prevWindow =
7866 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
7867 if (prevWindow && prevWindow->IsPopup()) {
7868 // Consume this message here since previous window must not have
7869 // been inactivated since we've already stopped accepting the
7870 // inactivation below.
7871 return true;
7873 } else if (LOWORD(aWParam) == WA_INACTIVE) {
7874 nsWindow* activeWindow =
7875 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
7876 if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) {
7877 // If focus moves to non-popup widget or focusable popup, the window
7878 // needs to update its nonclient area.
7879 if (!activeWindow || !activeWindow->IsPopup()) {
7880 sSendingNCACTIVATE = true;
7881 ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
7882 sSendingNCACTIVATE = false;
7884 sPendingNCACTIVATE = false;
7886 // If focus moves from/to popup, we don't need to rollup the popup
7887 // because such case is caused by strange mouse drivers. And in
7888 // such case, we should consume the message here since we need to
7889 // hide this odd focus move from our content. (If we didn't consume
7890 // the message here, ProcessMessage() will notify widget listener of
7891 // inactivation and that causes unnecessary reflow for supporting
7892 // -moz-window-inactive pseudo class.
7893 if (activeWindow) {
7894 if (activeWindow->IsPopup()) {
7895 return true;
7897 nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
7898 if (deactiveWindow && deactiveWindow->IsPopup()) {
7899 return true;
7902 } else if (LOWORD(aWParam) == WA_CLICKACTIVE) {
7903 // If the WM_ACTIVATE message is caused by a click in a popup,
7904 // we should not rollup any popups.
7905 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7906 if ((window && window->IsPopup()) ||
7907 !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7908 return false;
7911 allowAnimations = nsIRollupListener::AllowAnimations::No;
7912 } break;
7914 case MOZ_WM_REACTIVATE:
7915 // The previous active window should take back focus.
7916 if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
7917 // FYI: Even without this API call, you see expected result (e.g., the
7918 // owner window of the popup keeps active without flickering
7919 // the non-client area). And also this causes initializing
7920 // TSF and it causes using CPU time a lot. However, even if we
7921 // consume WM_ACTIVE messages, native focus change has already
7922 // been occurred. I.e., a popup window is active now. Therefore,
7923 // you'll see some odd behavior if we don't reactivate the owner
7924 // window here. For example, if you do:
7925 // 1. Turn wheel on a bookmark panel.
7926 // 2. Turn wheel on another window.
7927 // then, you'll see that the another window becomes active but the
7928 // owner window of the bookmark panel looks still active and the
7929 // bookmark panel keeps open. The reason is that the first wheel
7930 // operation gives focus to the bookmark panel. Therefore, when
7931 // the next operation gives focus to the another window, previous
7932 // focus window is the bookmark panel (i.e., a popup window).
7933 // So, in this case, our hack around here prevents to inactivate
7934 // the owner window and roll up the bookmark panel.
7935 ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
7937 return true;
7939 case WM_NCACTIVATE:
7940 if (!aWParam && !sSendingNCACTIVATE &&
7941 NeedsToHandleNCActivateDelayed(aWnd)) {
7942 // Don't just consume WM_NCACTIVATE. It doesn't handle only the
7943 // nonclient area state change.
7944 ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
7945 // Accept the deactivating because it's necessary to receive following
7946 // WM_ACTIVATE.
7947 *aResult = TRUE;
7948 sPendingNCACTIVATE = true;
7949 return true;
7951 return false;
7953 case WM_MOUSEACTIVATE:
7954 if (!EventIsInsideWindow(popupWindow) &&
7955 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7956 // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
7957 // of TweakUI is enabled. Then, check if the popup should be rolled up
7958 // with rollup listener. If not, just consume the message.
7959 if (HIWORD(aLParam) == WM_MOUSEMOVE &&
7960 !rollupListener->ShouldRollupOnMouseActivate()) {
7961 return true;
7963 // Otherwise, it should be handled by wndproc.
7964 return false;
7967 // Prevent the click inside the popup from causing a change in window
7968 // activation. Since the popup is shown non-activated, we need to eat any
7969 // requests to activate the window while it is displayed. Windows will
7970 // automatically activate the popup on the mousedown otherwise.
7971 return true;
7973 case WM_SHOWWINDOW:
7974 // If the window is being minimized, close popups.
7975 if (aLParam == SW_PARENTCLOSING) {
7976 allowAnimations = nsIRollupListener::AllowAnimations::No;
7977 break;
7979 return false;
7981 case WM_KILLFOCUS:
7982 // If focus moves to other window created in different process/thread,
7983 // e.g., a plugin window, popups should be rolled up.
7984 if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
7985 allowAnimations = nsIRollupListener::AllowAnimations::No;
7986 break;
7988 return false;
7990 case WM_MOVING:
7991 case WM_MENUSELECT:
7992 break;
7994 default:
7995 return false;
7998 // Only need to deal with the last rollup for left mouse down events.
7999 NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
8001 nsIRollupListener::RollupOptions rollupOptions{
8002 popupsToRollup,
8003 nsIRollupListener::FlushViews::Yes,
8004 /* mPoint = */ nullptr,
8005 allowAnimations,
8008 if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
8009 nativeMessage == WM_POINTERDOWN) {
8010 LayoutDeviceIntPoint pos;
8011 if (nativeMessage == WM_TOUCH) {
8012 pos.x = touchPoint->x;
8013 pos.y = touchPoint->y;
8014 } else {
8015 POINT pt;
8016 pt.x = GET_X_LPARAM(aLParam);
8017 pt.y = GET_Y_LPARAM(aLParam);
8018 // POINTERDOWN is already in screen coords.
8019 if (nativeMessage == WM_LBUTTONDOWN) {
8020 ::ClientToScreen(aWnd, &pt);
8022 pos = LayoutDeviceIntPoint(pt.x, pt.y);
8025 rollupOptions.mPoint = &pos;
8026 nsIContent* lastRollup = nullptr;
8027 consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
8028 nsAutoRollup::SetLastRollup(lastRollup);
8029 } else {
8030 consumeRollupEvent = rollupListener->Rollup(rollupOptions);
8033 // Tell hook to stop processing messages
8034 sProcessHook = false;
8035 sRollupMsgId = 0;
8036 sRollupMsgWnd = nullptr;
8038 // If we are NOT supposed to be consuming events, let it go through
8039 if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) {
8040 *aResult = MA_ACTIVATE;
8041 return true;
8044 return false;
8047 /**************************************************************
8048 **************************************************************
8050 ** BLOCK: Misc. utility methods and functions.
8052 ** General use.
8054 **************************************************************
8055 **************************************************************/
8057 // Note that the result of GetTopLevelWindow method can be different from the
8058 // result of WinUtils::GetTopLevelHWND(). The result can be non-floating
8059 // window. Because our top level window may be contained in another window
8060 // which is not managed by us.
8061 nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
8062 nsWindow* curWindow = this;
8064 while (true) {
8065 if (aStopOnDialogOrPopup) {
8066 switch (curWindow->mWindowType) {
8067 case WindowType::Dialog:
8068 case WindowType::Popup:
8069 return curWindow;
8070 default:
8071 break;
8075 // Retrieve the top level parent or owner window
8076 nsWindow* parentWindow = curWindow->GetParentWindow(true);
8078 if (!parentWindow) return curWindow;
8080 curWindow = parentWindow;
8084 // Set a flag if hwnd is a (non-popup) visible window from this process,
8085 // and bail out of the enumeration. Otherwise leave the flag unmodified
8086 // and continue the enumeration.
8087 // lParam must be a bool* pointing at the flag to be set.
8088 static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
8089 DWORD pid;
8090 ::GetWindowThreadProcessId(hwnd, &pid);
8091 if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
8092 // Don't count popups as visible windows, since they don't take focus,
8093 // in case we only have a popup visible (see bug 1554490 where the gfx
8094 // test window is an offscreen popup).
8095 nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
8096 if (!window || !window->IsPopup()) {
8097 bool* windowsVisible = reinterpret_cast<bool*>(lParam);
8098 *windowsVisible = true;
8099 return FALSE;
8102 return TRUE;
8105 // Determine if it would be ok to activate a window, taking focus.
8106 // We want to avoid stealing focus from another app (bug 225305).
8107 bool nsWindow::CanTakeFocus() {
8108 HWND fgWnd = ::GetForegroundWindow();
8109 if (!fgWnd) {
8110 // There is no foreground window, so don't worry about stealing focus.
8111 return true;
8113 // We can take focus if the current foreground window is already from
8114 // this process.
8115 DWORD pid;
8116 ::GetWindowThreadProcessId(fgWnd, &pid);
8117 if (pid == ::GetCurrentProcessId()) {
8118 return true;
8121 bool windowsVisible = false;
8122 ::EnumWindows(EnumVisibleWindowsProc,
8123 reinterpret_cast<LPARAM>(&windowsVisible));
8125 if (!windowsVisible) {
8126 // We're probably creating our first visible window, allow that to
8127 // take focus.
8128 return true;
8130 return false;
8133 /* static */ const wchar_t* nsWindow::GetMainWindowClass() {
8134 static const wchar_t* sMainWindowClass = nullptr;
8135 if (!sMainWindowClass) {
8136 nsAutoString className;
8137 Preferences::GetString("ui.window_class_override", className);
8138 if (!className.IsEmpty()) {
8139 sMainWindowClass = wcsdup(className.get());
8140 } else {
8141 sMainWindowClass = kClassNameGeneral;
8144 return sMainWindowClass;
8147 LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
8148 POINT pt;
8149 pt.x = GET_X_LPARAM(lParam);
8150 pt.y = GET_Y_LPARAM(lParam);
8151 ::ClientToScreen(mWnd, &pt);
8152 return MAKELPARAM(pt.x, pt.y);
8155 LPARAM nsWindow::lParamToClient(LPARAM lParam) {
8156 POINT pt;
8157 pt.x = GET_X_LPARAM(lParam);
8158 pt.y = GET_Y_LPARAM(lParam);
8159 ::ScreenToClient(mWnd, &pt);
8160 return MAKELPARAM(pt.x, pt.y);
8163 WPARAM nsWindow::wParamFromGlobalMouseState() {
8164 WPARAM result = 0;
8166 if (!!::GetKeyState(VK_CONTROL)) {
8167 result |= MK_CONTROL;
8170 if (!!::GetKeyState(VK_SHIFT)) {
8171 result |= MK_SHIFT;
8174 if (!!::GetKeyState(VK_LBUTTON)) {
8175 result |= MK_LBUTTON;
8178 if (!!::GetKeyState(VK_MBUTTON)) {
8179 result |= MK_MBUTTON;
8182 if (!!::GetKeyState(VK_RBUTTON)) {
8183 result |= MK_RBUTTON;
8186 if (!!::GetKeyState(VK_XBUTTON1)) {
8187 result |= MK_XBUTTON1;
8190 if (!!::GetKeyState(VK_XBUTTON2)) {
8191 result |= MK_XBUTTON2;
8194 return result;
8197 void nsWindow::PickerOpen() { mPickerDisplayCount++; }
8199 void nsWindow::PickerClosed() {
8200 NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
8201 if (!mPickerDisplayCount) return;
8202 mPickerDisplayCount--;
8203 if (!mPickerDisplayCount && mDestroyCalled) {
8204 Destroy();
8208 bool nsWindow::WidgetTypeSupportsAcceleration() {
8209 // We don't currently support using an accelerated layer manager with
8210 // transparent windows so don't even try. I'm also not sure if we even
8211 // want to support this case. See bug 593471.
8213 // Windows' support for transparent accelerated surfaces isn't great.
8214 // Some possible approaches:
8215 // - Readback the data and update it using
8216 // UpdateLayeredWindow/UpdateLayeredWindowIndirect
8217 // This is what WPF does. See
8218 // CD3DDeviceLevel1::PresentWithGDI/CD3DSwapChainWithSwDC in WpfGfx. The
8219 // rationale for not using IDirect3DSurface9::GetDC is explained here:
8220 // https://web.archive.org/web/20160521191104/https://blogs.msdn.microsoft.com/dwayneneed/2008/09/08/transparent-windows-in-wpf/
8221 // - Use D3D11_RESOURCE_MISC_GDI_COMPATIBLE, IDXGISurface1::GetDC(),
8222 // and UpdateLayeredWindowIndirect.
8223 // This is suggested here:
8224 // https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/december/windows-with-c-layered-windows-with-direct2d
8225 // but might have the same problem that IDirect3DSurface9::GetDC has.
8226 // - Creating the window with the WS_EX_NOREDIRECTIONBITMAP flag and use
8227 // DirectComposition.
8228 // Not supported on Win7.
8229 // - Using DwmExtendFrameIntoClientArea with negative margins and something
8230 // to turn off the glass effect.
8231 // This doesn't work when the DWM is not running (Win7)
8233 // Also see bug 1150376, D3D11 composition can cause issues on some devices
8234 // on Windows 7 where presentation fails randomly for windows with drop
8235 // shadows.
8236 return mTransparencyMode != TransparencyMode::Transparent &&
8237 !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
8240 bool nsWindow::DispatchTouchEventFromWMPointer(
8241 UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo,
8242 mozilla::MouseButton aButton) {
8243 MultiTouchInput::MultiTouchType touchType;
8244 switch (msg) {
8245 case WM_POINTERDOWN:
8246 touchType = MultiTouchInput::MULTITOUCH_START;
8247 break;
8248 case WM_POINTERUPDATE:
8249 if (aPointerInfo.mPressure == 0) {
8250 return false; // hover
8252 touchType = MultiTouchInput::MULTITOUCH_MOVE;
8253 break;
8254 case WM_POINTERUP:
8255 touchType = MultiTouchInput::MULTITOUCH_END;
8256 break;
8257 default:
8258 return false;
8261 nsPointWin touchPoint;
8262 touchPoint.x = GET_X_LPARAM(aLParam);
8263 touchPoint.y = GET_Y_LPARAM(aLParam);
8264 touchPoint.ScreenToClient(mWnd);
8266 SingleTouchData touchData(static_cast<int32_t>(aPointerInfo.pointerId),
8267 ScreenIntPoint::FromUnknownPoint(touchPoint),
8268 ScreenSize(1, 1), // pixel size radius for pen
8269 0.0f, // no radius rotation
8270 aPointerInfo.mPressure);
8271 touchData.mTiltX = aPointerInfo.tiltX;
8272 touchData.mTiltY = aPointerInfo.tiltY;
8273 touchData.mTwist = aPointerInfo.twist;
8275 MultiTouchInput touchInput;
8276 touchInput.mType = touchType;
8277 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
8278 touchInput.mTouches.AppendElement(touchData);
8279 touchInput.mButton = aButton;
8280 touchInput.mButtons = aPointerInfo.mButtons;
8282 // POINTER_INFO.dwKeyStates can't be used as it only supports Shift and Ctrl
8283 ModifierKeyState modifierKeyState;
8284 touchInput.modifiers = modifierKeyState.GetModifiers();
8286 DispatchTouchInput(touchInput, MouseEvent_Binding::MOZ_SOURCE_PEN);
8287 return true;
8290 static MouseButton PenFlagsToMouseButton(PEN_FLAGS aPenFlags) {
8291 // Theoretically flags can be set together but they do not
8292 if (aPenFlags & PEN_FLAG_BARREL) {
8293 return MouseButton::eSecondary;
8295 if (aPenFlags & PEN_FLAG_ERASER) {
8296 return MouseButton::eEraser;
8298 return MouseButton::ePrimary;
8301 bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
8302 if (!mAPZC) {
8303 // APZ is not available on context menu. Follow the behavior of touch input
8304 // which fallbacks to WM_LBUTTON* and WM_GESTURE, to keep consistency.
8305 return false;
8307 if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) {
8308 return false;
8310 if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
8311 // We have to handle WM_POINTER* to fetch and cache pen related information
8312 // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
8313 // handler. This is because Windows doesn't support ::DoDragDrop in the
8314 // touch or pen message handlers.
8315 mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam);
8316 // Don't consume the Windows WM_POINTER* messages
8317 return false;
8320 uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
8321 POINTER_PEN_INFO penInfo{};
8322 if (!mPointerEvents.GetPointerPenInfo(pointerId, &penInfo)) {
8323 return false;
8326 // When dispatching mouse events with pen, there may be some
8327 // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
8328 // small movements. Those events will reset sLastMousePoint and reset
8329 // sLastClickCount. To prevent that, we keep the last pen down position
8330 // and compare it with the subsequent WM_POINTERUPDATE. If the movement is
8331 // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
8332 // eMouseMove for WM_POINTERUPDATE.
8333 static POINT sLastPointerDownPoint = {0};
8335 // We don't support chorded buttons for pen. Keep the button at
8336 // WM_POINTERDOWN.
8337 static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
8338 static bool sPointerDown = false;
8340 EventMessage message;
8341 mozilla::MouseButton button = MouseButton::ePrimary;
8342 switch (msg) {
8343 case WM_POINTERDOWN: {
8344 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8345 GET_Y_LPARAM(aLParam));
8346 sLastPointerDownPoint.x = eventPoint.x;
8347 sLastPointerDownPoint.y = eventPoint.y;
8348 message = eMouseDown;
8349 button = PenFlagsToMouseButton(penInfo.penFlags);
8350 sLastPenDownButton = button;
8351 sPointerDown = true;
8352 } break;
8353 case WM_POINTERUP:
8354 message = eMouseUp;
8355 MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
8356 button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
8357 sPointerDown = false;
8358 break;
8359 case WM_POINTERUPDATE:
8360 message = eMouseMove;
8361 if (sPointerDown) {
8362 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8363 GET_Y_LPARAM(aLParam));
8364 int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
8365 ? sLastPointerDownPoint.x - eventPoint.x.value
8366 : eventPoint.x.value - sLastPointerDownPoint.x;
8367 int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
8368 ? sLastPointerDownPoint.y - eventPoint.y.value
8369 : eventPoint.y.value - sLastPointerDownPoint.y;
8370 bool insideMovementThreshold =
8371 movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
8372 movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
8374 if (insideMovementThreshold) {
8375 // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
8376 // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
8377 return false;
8379 button = sLastPenDownButton;
8381 break;
8382 case WM_POINTERLEAVE:
8383 message = eMouseExitFromWidget;
8384 break;
8385 default:
8386 return false;
8389 // Windows defines the pen pressure is normalized to a range between 0 and
8390 // 1024. Convert it to float.
8391 float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
8392 int16_t buttons = sPointerDown
8393 ? nsContentUtils::GetButtonsFlagForButton(button)
8394 : MouseButtonsFlag::eNoButtons;
8395 WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure,
8396 buttons);
8397 // Per
8398 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_pen_info,
8399 // the rotation is normalized in a range of 0 to 359.
8400 MOZ_ASSERT(penInfo.rotation <= 359);
8401 pointerInfo.twist = (int32_t)penInfo.rotation;
8403 // Fire touch events but not when the barrel button is pressed.
8404 if (button != MouseButton::eSecondary &&
8405 StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled() &&
8406 DispatchTouchEventFromWMPointer(msg, aLParam, pointerInfo, button)) {
8407 return true;
8410 // The aLParam of WM_POINTER* is the screen location. Convert it to client
8411 // location
8412 LPARAM newLParam = lParamToClient(aLParam);
8413 DispatchMouseEvent(message, aWParam, newLParam, false, button,
8414 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8416 if (button == MouseButton::eSecondary && message == eMouseUp) {
8417 // Fire eContextMenu manually since consuming WM_POINTER* blocks
8418 // WM_CONTEXTMENU
8419 DispatchMouseEvent(eContextMenu, aWParam, newLParam, false, button,
8420 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8422 // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
8423 // WM_MOUSEMOVE.
8424 return true;
8427 void nsWindow::GetCompositorWidgetInitData(
8428 mozilla::widget::CompositorWidgetInitData* aInitData) {
8429 *aInitData = WinCompositorWidgetInitData(
8430 reinterpret_cast<uintptr_t>(mWnd),
8431 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
8432 mTransparencyMode, mFrameState->GetSizeMode());
8435 bool nsWindow::SynchronouslyRepaintOnResize() { return false; }
8437 void nsWindow::MaybeDispatchInitialFocusEvent() {
8438 if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
8439 DispatchFocusToTopLevelWindow(true);
8443 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
8444 nsCOMPtr<nsIWidget> window = new nsWindow();
8445 return window.forget();
8448 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
8449 nsCOMPtr<nsIWidget> window = new nsWindow(true);
8450 return window.forget();
8453 // static
8454 bool nsWindow::InitTouchInjection() {
8455 if (!sTouchInjectInitialized) {
8456 // Initialize touch injection on the first call
8457 HMODULE hMod = LoadLibraryW(kUser32LibName);
8458 if (!hMod) {
8459 return false;
8462 InitializeTouchInjectionPtr func =
8463 (InitializeTouchInjectionPtr)GetProcAddress(hMod,
8464 "InitializeTouchInjection");
8465 if (!func) {
8466 WinUtils::Log("InitializeTouchInjection not available.");
8467 return false;
8470 if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
8471 WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
8472 GetLastError());
8473 return false;
8476 sInjectTouchFuncPtr =
8477 (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
8478 if (!sInjectTouchFuncPtr) {
8479 WinUtils::Log("InjectTouchInput not available.");
8480 return false;
8482 sTouchInjectInitialized = true;
8484 return true;
8487 bool nsWindow::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
8488 POINTER_FLAGS aFlags, uint32_t aPressure,
8489 uint32_t aOrientation) {
8490 if (aId > TOUCH_INJECT_MAX_POINTS) {
8491 WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
8492 return false;
8495 POINTER_TOUCH_INFO info{};
8497 info.touchFlags = TOUCH_FLAG_NONE;
8498 info.touchMask =
8499 TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
8500 info.pressure = aPressure;
8501 info.orientation = aOrientation;
8503 info.pointerInfo.pointerFlags = aFlags;
8504 info.pointerInfo.pointerType = PT_TOUCH;
8505 info.pointerInfo.pointerId = aId;
8506 info.pointerInfo.ptPixelLocation.x = aPoint.x;
8507 info.pointerInfo.ptPixelLocation.y = aPoint.y;
8509 info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
8510 info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
8511 info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
8512 info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
8514 for (int i = 0; i < 3; i++) {
8515 if (sInjectTouchFuncPtr(1, &info)) {
8516 break;
8518 DWORD error = GetLastError();
8519 if (error == ERROR_NOT_READY && i < 2) {
8520 // We sent it too quickly after the previous injection (see bug 1535140
8521 // comment 10). On the first loop iteration we just yield (via Sleep(0))
8522 // and try again. If it happens again on the second loop iteration we
8523 // explicitly Sleep(1) and try again. If that doesn't work either we just
8524 // error out.
8525 ::Sleep(i);
8526 continue;
8528 WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
8529 return false;
8531 return true;
8534 void nsWindow::ChangedDPI() {
8535 if (mWidgetListener) {
8536 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
8537 presShell->BackingScaleFactorChanged();
8542 static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
8543 nsWindow::TouchPointerState aPointerState, bool isUpdate) {
8544 bool hover = aPointerState & nsWindow::TOUCH_HOVER;
8545 bool contact = aPointerState & nsWindow::TOUCH_CONTACT;
8546 bool remove = aPointerState & nsWindow::TOUCH_REMOVE;
8547 bool cancel = aPointerState & nsWindow::TOUCH_CANCEL;
8549 POINTER_FLAGS flags;
8550 if (isUpdate) {
8551 // We know about this pointer, send an update
8552 flags = POINTER_FLAG_UPDATE;
8553 if (hover) {
8554 flags |= POINTER_FLAG_INRANGE;
8555 } else if (contact) {
8556 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
8557 } else if (remove) {
8558 flags = POINTER_FLAG_UP;
8561 if (cancel) {
8562 flags |= POINTER_FLAG_CANCELED;
8564 } else {
8565 // Missing init state, error out
8566 if (remove || cancel) {
8567 return Err(NS_ERROR_INVALID_ARG);
8570 // Create a new pointer
8571 flags = POINTER_FLAG_INRANGE;
8572 if (contact) {
8573 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
8576 return flags;
8579 nsresult nsWindow::SynthesizeNativeTouchPoint(
8580 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8581 LayoutDeviceIntPoint aPoint, double aPointerPressure,
8582 uint32_t aPointerOrientation, nsIObserver* aObserver) {
8583 AutoObserverNotifier notifier(aObserver, "touchpoint");
8585 if (StaticPrefs::apz_test_fails_with_native_injection() ||
8586 !InitTouchInjection()) {
8587 // If we don't have touch injection from the OS, or if we are running a test
8588 // that cannot properly inject events to satisfy the OS requirements (see
8589 // bug 1313170) we can just fake it and synthesize the events from here.
8590 MOZ_ASSERT(NS_IsMainThread());
8591 if (aPointerState == TOUCH_HOVER) {
8592 return NS_ERROR_UNEXPECTED;
8595 if (!mSynthesizedTouchInput) {
8596 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
8599 WidgetEventTime time = CurrentMessageWidgetEventTime();
8600 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8601 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
8602 mSynthesizedTouchInput.get(), time.mTimeStamp, aPointerId,
8603 aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
8604 DispatchTouchInput(inputToDispatch);
8605 return NS_OK;
8608 // win api expects a value from 0 to 1024. aPointerPressure is a value
8609 // from 0.0 to 1.0.
8610 uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
8612 // If we already know about this pointer id get it's record
8613 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8614 POINTER_FLAGS flags;
8615 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8616 auto result = PointerStateToFlag(aPointerState, !!entry);
8617 if (result.isOk()) {
8618 flags = result.unwrap();
8619 } else {
8620 return result.unwrapErr();
8623 if (!entry) {
8624 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8625 PointerInfo::PointerType::TOUCH));
8626 } else {
8627 if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
8628 return NS_ERROR_UNEXPECTED;
8630 if (aPointerState & TOUCH_REMOVE) {
8631 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8632 // so shouldn't leak.
8633 entry.Remove();
8637 return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
8638 aPointerOrientation)
8639 ? NS_ERROR_UNEXPECTED
8640 : NS_OK;
8644 nsresult nsWindow::ClearNativeTouchSequence(nsIObserver* aObserver) {
8645 AutoObserverNotifier notifier(aObserver, "cleartouch");
8646 if (!sTouchInjectInitialized) {
8647 return NS_OK;
8650 // cancel all input points
8651 for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
8652 auto* info = iter.UserData();
8653 if (info->mType != PointerInfo::PointerType::TOUCH) {
8654 continue;
8656 InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
8657 iter.Remove();
8660 nsBaseWidget::ClearNativeTouchSequence(nullptr);
8662 return NS_OK;
8665 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8666 static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
8667 static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
8668 static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
8669 #endif
8670 static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
8672 static bool InitPenInjection() {
8673 if (sSyntheticPenDevice) {
8674 return true;
8676 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8677 HMODULE hMod = LoadLibraryW(kUser32LibName);
8678 if (!hMod) {
8679 return false;
8681 CreateSyntheticPointerDevice =
8682 (CreateSyntheticPointerDevicePtr)GetProcAddress(
8683 hMod, "CreateSyntheticPointerDevice");
8684 if (!CreateSyntheticPointerDevice) {
8685 WinUtils::Log("CreateSyntheticPointerDevice not available.");
8686 return false;
8688 DestroySyntheticPointerDevice =
8689 (DestroySyntheticPointerDevicePtr)GetProcAddress(
8690 hMod, "DestroySyntheticPointerDevice");
8691 if (!DestroySyntheticPointerDevice) {
8692 WinUtils::Log("DestroySyntheticPointerDevice not available.");
8693 return false;
8695 InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
8696 hMod, "InjectSyntheticPointerInput");
8697 if (!InjectSyntheticPointerInput) {
8698 WinUtils::Log("InjectSyntheticPointerInput not available.");
8699 return false;
8701 #endif
8702 sSyntheticPenDevice =
8703 CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
8704 return !!sSyntheticPenDevice;
8707 nsresult nsWindow::SynthesizeNativePenInput(
8708 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8709 LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
8710 int32_t aTiltX, int32_t aTiltY, int32_t aButton, nsIObserver* aObserver) {
8711 AutoObserverNotifier notifier(aObserver, "peninput");
8712 if (!InitPenInjection()) {
8713 return NS_ERROR_UNEXPECTED;
8716 // win api expects a value from 0 to 1024. aPointerPressure is a value
8717 // from 0.0 to 1.0.
8718 uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
8720 // If we already know about this pointer id get it's record
8721 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8722 POINTER_FLAGS flags;
8723 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8724 auto result = PointerStateToFlag(aPointerState, !!entry);
8725 if (result.isOk()) {
8726 flags = result.unwrap();
8727 } else {
8728 return result.unwrapErr();
8731 if (!entry) {
8732 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8733 PointerInfo::PointerType::PEN));
8734 } else {
8735 if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
8736 return NS_ERROR_UNEXPECTED;
8738 if (aPointerState & TOUCH_REMOVE) {
8739 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8740 // so shouldn't leak.
8741 entry.Remove();
8745 POINTER_TYPE_INFO info{};
8747 info.type = PT_PEN;
8748 info.penInfo.pointerInfo.pointerType = PT_PEN;
8749 info.penInfo.pointerInfo.pointerFlags = flags;
8750 info.penInfo.pointerInfo.pointerId = aPointerId;
8751 info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
8752 info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
8754 info.penInfo.penFlags = PEN_FLAG_NONE;
8755 // PEN_FLAG_ERASER is not supported this way, unfortunately.
8756 if (aButton == 2) {
8757 info.penInfo.penFlags |= PEN_FLAG_BARREL;
8759 info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
8760 PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
8761 info.penInfo.pressure = pressure;
8762 info.penInfo.rotation = aRotation;
8763 info.penInfo.tiltX = aTiltX;
8764 info.penInfo.tiltY = aTiltY;
8766 return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
8767 ? NS_OK
8768 : NS_ERROR_UNEXPECTED;
8772 bool nsWindow::HandleAppCommandMsg(const MSG& aAppCommandMsg,
8773 LRESULT* aRetValue) {
8774 ModifierKeyState modKeyState;
8775 NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
8776 bool consumed = nativeKey.HandleAppCommandMessage();
8777 *aRetValue = consumed ? 1 : 0;
8778 return consumed;
8781 #ifdef DEBUG
8782 nsresult nsWindow::SetHiDPIMode(bool aHiDPI) {
8783 return WinUtils::SetHiDPIMode(aHiDPI);
8786 nsresult nsWindow::RestoreHiDPIMode() { return WinUtils::RestoreHiDPIMode(); }
8787 #endif
8789 mozilla::Maybe<UINT> nsWindow::GetHiddenTaskbarEdge() {
8790 HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
8792 // Check all four sides of our monitor for an appbar. Skip any that aren't
8793 // the system taskbar.
8794 MONITORINFO mi;
8795 mi.cbSize = sizeof(MONITORINFO);
8796 ::GetMonitorInfo(windowMonitor, &mi);
8798 APPBARDATA appBarData;
8799 appBarData.cbSize = sizeof(appBarData);
8800 appBarData.rc = mi.rcMonitor;
8801 const auto kEdges = {ABE_BOTTOM, ABE_TOP, ABE_LEFT, ABE_RIGHT};
8802 for (auto edge : kEdges) {
8803 appBarData.uEdge = edge;
8804 HWND appBarHwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &appBarData);
8805 if (appBarHwnd) {
8806 nsAutoString className;
8807 if (WinUtils::GetClassName(appBarHwnd, className)) {
8808 if (className.Equals(L"Shell_TrayWnd") ||
8809 className.Equals(L"Shell_SecondaryTrayWnd")) {
8810 return Some(edge);
8816 return Nothing();
8819 static nsSizeMode GetSizeModeForWindowFrame(HWND aWnd, bool aFullscreenMode) {
8820 WINDOWPLACEMENT pl;
8821 pl.length = sizeof(pl);
8822 ::GetWindowPlacement(aWnd, &pl);
8824 if (pl.showCmd == SW_SHOWMINIMIZED) {
8825 return nsSizeMode_Minimized;
8826 } else if (aFullscreenMode) {
8827 return nsSizeMode_Fullscreen;
8828 } else if (pl.showCmd == SW_SHOWMAXIMIZED) {
8829 return nsSizeMode_Maximized;
8830 } else {
8831 return nsSizeMode_Normal;
8835 static void ShowWindowWithMode(HWND aWnd, nsSizeMode aMode) {
8836 // This will likely cause a callback to
8837 // nsWindow::FrameState::{OnFrameChanging() and OnFrameChanged()}
8838 switch (aMode) {
8839 case nsSizeMode_Fullscreen:
8840 ::ShowWindow(aWnd, SW_SHOW);
8841 break;
8843 case nsSizeMode_Maximized:
8844 ::ShowWindow(aWnd, SW_MAXIMIZE);
8845 break;
8847 case nsSizeMode_Minimized:
8848 ::ShowWindow(aWnd, SW_MINIMIZE);
8849 break;
8851 default:
8852 // Don't call ::ShowWindow if we're trying to "restore" a window that is
8853 // already in a normal state. Prevents a bug where snapping to one side
8854 // of the screen and then minimizing would cause Windows to forget our
8855 // window's correct restored position/size.
8856 if (GetCurrentShowCmd(aWnd) != SW_SHOWNORMAL) {
8857 ::ShowWindow(aWnd, SW_RESTORE);
8862 nsWindow::FrameState::FrameState(nsWindow* aWindow) : mWindow(aWindow) {}
8864 nsSizeMode nsWindow::FrameState::GetSizeMode() const { return mSizeMode; }
8866 void nsWindow::FrameState::CheckInvariant() const {
8867 MOZ_ASSERT(mSizeMode >= 0 && mSizeMode < nsSizeMode_Invalid);
8868 MOZ_ASSERT(mLastSizeMode >= 0 && mLastSizeMode < nsSizeMode_Invalid);
8869 MOZ_ASSERT(mPreFullscreenSizeMode >= 0 &&
8870 mPreFullscreenSizeMode < nsSizeMode_Invalid);
8871 MOZ_ASSERT(mWindow);
8873 // We should never observe fullscreen sizemode unless fullscreen is enabled
8874 MOZ_ASSERT_IF(mSizeMode == nsSizeMode_Fullscreen, mFullscreenMode);
8875 MOZ_ASSERT_IF(!mFullscreenMode, mSizeMode != nsSizeMode_Fullscreen);
8877 // Something went wrong if we somehow saved fullscreen mode when we are
8878 // changing into fullscreen mode
8879 MOZ_ASSERT(mPreFullscreenSizeMode != nsSizeMode_Fullscreen);
8882 void nsWindow::FrameState::ConsumePreXULSkeletonState(bool aWasMaximized) {
8883 mSizeMode = aWasMaximized ? nsSizeMode_Maximized : nsSizeMode_Normal;
8886 void nsWindow::FrameState::EnsureSizeMode(nsSizeMode aMode,
8887 DoShowWindow aDoShowWindow) {
8888 if (mSizeMode == aMode) {
8889 return;
8892 if (StaticPrefs::widget_windows_fullscreen_remind_taskbar()) {
8893 // If we're unminimizing a window, asynchronously notify the taskbar after
8894 // the message has been processed. This redundant notification works around
8895 // a race condition in explorer.exe. (See bug 1835851, or comments in
8896 // TaskbarConcealer.)
8898 // Note that we notify regardless of `aMode`: unminimizing a non-fullscreen
8899 // window can also affect the correct taskbar state, yet fail to affect the
8900 // current taskbar state.
8901 if (mSizeMode == nsSizeMode_Minimized) {
8902 ::PostMessage(mWindow->mWnd, MOZ_WM_FULLSCREEN_STATE_UPDATE, 0, 0);
8906 if (aMode == nsSizeMode_Fullscreen) {
8907 EnsureFullscreenMode(true, aDoShowWindow);
8908 MOZ_ASSERT(mSizeMode == nsSizeMode_Fullscreen);
8909 } else if (mSizeMode == nsSizeMode_Fullscreen && aMode == nsSizeMode_Normal) {
8910 // If we are in fullscreen mode, minimize should work like normal and
8911 // return us to fullscreen mode when unminimized. Maximize isn't really
8912 // available and won't do anything. "Restore" should do the same thing as
8913 // requesting to end fullscreen.
8914 EnsureFullscreenMode(false, aDoShowWindow);
8915 } else {
8916 SetSizeModeInternal(aMode, aDoShowWindow);
8920 void nsWindow::FrameState::EnsureFullscreenMode(bool aFullScreen,
8921 DoShowWindow aDoShowWindow) {
8922 const bool changed = aFullScreen != mFullscreenMode;
8923 if (changed && aFullScreen) {
8924 // Save the size mode from before fullscreen.
8925 mPreFullscreenSizeMode = mSizeMode;
8927 mFullscreenMode = aFullScreen;
8928 if (changed || aFullScreen) {
8929 // NOTE(emilio): When minimizing a fullscreen window we remain with
8930 // mFullscreenMode = true, but mSizeMode = nsSizeMode_Minimized. We need to
8931 // make sure to call SetSizeModeInternal even if mFullscreenMode didn't
8932 // change, to ensure we actually end up with a fullscreen sizemode when
8933 // restoring a window from that state.
8934 SetSizeModeInternal(
8935 aFullScreen ? nsSizeMode_Fullscreen : mPreFullscreenSizeMode,
8936 aDoShowWindow);
8940 void nsWindow::FrameState::OnFrameChanging() {
8941 const nsSizeMode newSizeMode =
8942 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
8943 EnsureSizeMode(newSizeMode);
8944 mWindow->UpdateNonClientMargins(false);
8947 void nsWindow::FrameState::OnFrameChanged() {
8948 // We don't want to perform the ShowWindow ourselves if we're on the frame
8949 // changed message. Windows has done the frame change for us, and we take care
8950 // of activating as needed. We also don't want to potentially trigger
8951 // more focus / restore. Among other things, this addresses a bug on Win7
8952 // related to window docking. (bug 489258)
8953 const auto newSizeMode =
8954 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
8955 EnsureSizeMode(newSizeMode, DoShowWindow::No);
8957 // If window was restored, activate the window now to get correct attributes.
8958 if (mWindow->mIsVisible && mWindow->IsForegroundWindow() &&
8959 mLastSizeMode == nsSizeMode_Minimized &&
8960 mSizeMode != nsSizeMode_Minimized) {
8961 mWindow->DispatchFocusToTopLevelWindow(true);
8963 mLastSizeMode = mSizeMode;
8966 static void MaybeLogSizeMode(nsSizeMode aMode) {
8967 #ifdef WINSTATE_DEBUG_OUTPUT
8968 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** SizeMode: %d\n", int(aMode)));
8969 #endif
8972 void nsWindow::FrameState::SetSizeModeInternal(nsSizeMode aMode,
8973 DoShowWindow aDoShowWindow) {
8974 if (mSizeMode == aMode) {
8975 return;
8978 const auto oldSizeMode = mSizeMode;
8979 const bool fullscreenChange =
8980 mSizeMode == nsSizeMode_Fullscreen || aMode == nsSizeMode_Fullscreen;
8981 const bool fullscreen = aMode == nsSizeMode_Fullscreen;
8983 mLastSizeMode = mSizeMode;
8984 mSizeMode = aMode;
8986 MaybeLogSizeMode(mSizeMode);
8988 if (bool(aDoShowWindow) && mWindow->mIsVisible) {
8989 ShowWindowWithMode(mWindow->mWnd, aMode);
8992 mWindow->UpdateNonClientMargins(false);
8994 if (fullscreenChange) {
8995 mWindow->OnFullscreenChanged(oldSizeMode, fullscreen);
8998 mWindow->OnSizeModeChange();
9001 void nsWindow::ContextMenuPreventer::Update(
9002 const WidgetMouseEvent& aEvent,
9003 const nsIWidget::ContentAndAPZEventStatus& aEventStatus) {
9004 mNeedsToPreventContextMenu =
9005 aEvent.mMessage == eMouseUp &&
9006 aEvent.mButton == MouseButton::eSecondary &&
9007 aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
9008 aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault;