1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #include "WinWindowOcclusionTracker.h"
14 #include "base/thread.h"
15 #include "base/message_loop.h"
16 #include "base/platform_thread.h"
17 #include "gfxConfig.h"
18 #include "nsThreadUtils.h"
19 #include "mozilla/DataMutex.h"
20 #include "mozilla/gfx/Logging.h"
21 #include "mozilla/TimeStamp.h"
22 #include "mozilla/Logging.h"
23 #include "mozilla/StaticPrefs_widget.h"
24 #include "mozilla/StaticPtr.h"
25 #include "nsBaseWidget.h"
27 #include "transport/runnable_utils.h"
28 #include "WinEventObserver.h"
31 namespace mozilla::widget
{
33 // Can be called on Main thread
34 LazyLogModule
gWinOcclusionTrackerLog("WinOcclusionTracker");
35 #define LOG(type, ...) MOZ_LOG(gWinOcclusionTrackerLog, type, (__VA_ARGS__))
37 // Can be called on OcclusionCalculator thread
38 LazyLogModule
gWinOcclusionCalculatorLog("WinOcclusionCalculator");
39 #define CALC_LOG(type, ...) \
40 MOZ_LOG(gWinOcclusionCalculatorLog, type, (__VA_ARGS__))
42 // ~16 ms = time between frames when frame rate is 60 FPS.
43 const int kOcclusionUpdateRunnableDelayMs
= 16;
45 class OcclusionUpdateRunnable
: public CancelableRunnable
{
47 explicit OcclusionUpdateRunnable(
48 WinWindowOcclusionTracker::WindowOcclusionCalculator
*
50 : CancelableRunnable("OcclusionUpdateRunnable"),
51 mOcclusionCalculator(aOcclusionCalculator
) {
52 mTimeStamp
= TimeStamp::Now();
55 NS_IMETHOD
Run() override
{
59 MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
62 round((TimeStamp::Now() - mTimeStamp
).ToMilliseconds());
63 CALC_LOG(LogLevel::Debug
,
64 "ComputeNativeWindowOcclusionStatus() latencyMs %u", latencyMs
);
66 mOcclusionCalculator
->ComputeNativeWindowOcclusionStatus();
70 nsresult
Cancel() override
{
72 mOcclusionCalculator
= nullptr;
77 bool mIsCanceled
= false;
78 RefPtr
<WinWindowOcclusionTracker::WindowOcclusionCalculator
>
83 // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
84 class SerializedTaskDispatcher
{
85 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SerializedTaskDispatcher
)
88 SerializedTaskDispatcher();
91 void PostTaskToMain(already_AddRefed
<nsIRunnable
> aTask
);
92 void PostTaskToCalculator(already_AddRefed
<nsIRunnable
> aTask
);
93 void PostDelayedTaskToCalculator(already_AddRefed
<Runnable
> aTask
,
95 bool IsOnCurrentThread();
98 friend class DelayedTaskRunnable
;
100 ~SerializedTaskDispatcher();
103 std::queue
<std::pair
<RefPtr
<nsIRunnable
>, RefPtr
<nsISerialEventTarget
>>>
105 bool mDestroyed
= false;
106 RefPtr
<Runnable
> mCurrentRunnable
;
109 void PostTasksIfNecessary(nsISerialEventTarget
* aEventTarget
,
110 const DataMutex
<Data
>::AutoLock
& aProofOfLock
);
111 void HandleDelayedTask(already_AddRefed
<nsIRunnable
> aTask
);
114 // Hold current EventTarget during calling nsIRunnable::Run().
115 RefPtr
<nsISerialEventTarget
> mCurrentEventTarget
= nullptr;
117 DataMutex
<Data
> mData
;
120 class DelayedTaskRunnable
: public Runnable
{
122 DelayedTaskRunnable(SerializedTaskDispatcher
* aSerializedTaskDispatcher
,
123 already_AddRefed
<Runnable
> aTask
)
124 : Runnable("DelayedTaskRunnable"),
125 mSerializedTaskDispatcher(aSerializedTaskDispatcher
),
128 NS_IMETHOD
Run() override
{
129 mSerializedTaskDispatcher
->HandleDelayedTask(mTask
.forget());
134 RefPtr
<SerializedTaskDispatcher
> mSerializedTaskDispatcher
;
135 RefPtr
<Runnable
> mTask
;
138 SerializedTaskDispatcher::SerializedTaskDispatcher()
139 : mData("SerializedTaskDispatcher::mData") {
140 MOZ_RELEASE_ASSERT(NS_IsMainThread());
142 "SerializedTaskDispatcher::SerializedTaskDispatcher() this %p", this);
145 SerializedTaskDispatcher::~SerializedTaskDispatcher() {
147 auto data
= mData
.Lock();
148 MOZ_ASSERT(data
->mDestroyed
);
149 MOZ_ASSERT(data
->mTasks
.empty());
153 void SerializedTaskDispatcher::Destroy() {
154 MOZ_RELEASE_ASSERT(NS_IsMainThread());
155 LOG(LogLevel::Info
, "SerializedTaskDispatcher::Destroy() this %p", this);
157 auto data
= mData
.Lock();
158 if (data
->mDestroyed
) {
162 data
->mDestroyed
= true;
163 std::queue
<std::pair
<RefPtr
<nsIRunnable
>, RefPtr
<nsISerialEventTarget
>>>
165 std::swap(data
->mTasks
, empty
);
168 void SerializedTaskDispatcher::PostTaskToMain(
169 already_AddRefed
<nsIRunnable
> aTask
) {
170 RefPtr
<nsIRunnable
> task
= aTask
;
172 auto data
= mData
.Lock();
173 if (data
->mDestroyed
) {
177 nsISerialEventTarget
* eventTarget
= GetMainThreadSerialEventTarget();
178 data
->mTasks
.push({std::move(task
), eventTarget
});
180 MOZ_ASSERT_IF(!data
->mCurrentRunnable
, data
->mTasks
.size() == 1);
181 PostTasksIfNecessary(eventTarget
, data
);
184 void SerializedTaskDispatcher::PostTaskToCalculator(
185 already_AddRefed
<nsIRunnable
> aTask
) {
186 RefPtr
<nsIRunnable
> task
= aTask
;
188 auto data
= mData
.Lock();
189 if (data
->mDestroyed
) {
193 nsISerialEventTarget
* eventTarget
=
194 WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
195 data
->mTasks
.push({std::move(task
), eventTarget
});
197 MOZ_ASSERT_IF(!data
->mCurrentRunnable
, data
->mTasks
.size() == 1);
198 PostTasksIfNecessary(eventTarget
, data
);
201 void SerializedTaskDispatcher::PostDelayedTaskToCalculator(
202 already_AddRefed
<Runnable
> aTask
, int aDelayMs
) {
203 CALC_LOG(LogLevel::Debug
,
204 "SerializedTaskDispatcher::PostDelayedTaskToCalculator()");
206 RefPtr
<DelayedTaskRunnable
> runnable
=
207 new DelayedTaskRunnable(this, std::move(aTask
));
208 MessageLoop
* targetLoop
=
209 WinWindowOcclusionTracker::OcclusionCalculatorLoop();
210 targetLoop
->PostDelayedTask(runnable
.forget(), aDelayMs
);
213 bool SerializedTaskDispatcher::IsOnCurrentThread() {
214 return !!mCurrentEventTarget
;
217 void SerializedTaskDispatcher::PostTasksIfNecessary(
218 nsISerialEventTarget
* aEventTarget
,
219 const DataMutex
<Data
>::AutoLock
& aProofOfLock
) {
220 MOZ_ASSERT(!aProofOfLock
->mTasks
.empty());
222 if (aProofOfLock
->mCurrentRunnable
) {
226 RefPtr
<Runnable
> runnable
=
227 WrapRunnable(RefPtr
<SerializedTaskDispatcher
>(this),
228 &SerializedTaskDispatcher::HandleTasks
);
229 aProofOfLock
->mCurrentRunnable
= runnable
;
230 aEventTarget
->Dispatch(runnable
.forget());
233 void SerializedTaskDispatcher::HandleDelayedTask(
234 already_AddRefed
<nsIRunnable
> aTask
) {
235 MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
236 CALC_LOG(LogLevel::Debug
, "SerializedTaskDispatcher::HandleDelayedTask()");
238 RefPtr
<nsIRunnable
> task
= aTask
;
240 auto data
= mData
.Lock();
241 if (data
->mDestroyed
) {
245 nsISerialEventTarget
* eventTarget
=
246 WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
247 data
->mTasks
.push({std::move(task
), eventTarget
});
249 MOZ_ASSERT_IF(!data
->mCurrentRunnable
, data
->mTasks
.size() == 1);
250 PostTasksIfNecessary(eventTarget
, data
);
253 void SerializedTaskDispatcher::HandleTasks() {
254 RefPtr
<nsIRunnable
> frontTask
;
258 auto data
= mData
.Lock();
259 if (data
->mDestroyed
) {
262 MOZ_RELEASE_ASSERT(data
->mCurrentRunnable
);
263 MOZ_RELEASE_ASSERT(!data
->mTasks
.empty());
265 frontTask
= data
->mTasks
.front().first
;
267 MOZ_RELEASE_ASSERT(!mCurrentEventTarget
);
268 mCurrentEventTarget
= data
->mTasks
.front().second
;
272 if (NS_IsMainThread()) {
273 LOG(LogLevel::Debug
, "SerializedTaskDispatcher::HandleTasks()");
275 CALC_LOG(LogLevel::Debug
, "SerializedTaskDispatcher::HandleTasks()");
278 MOZ_ASSERT_IF(NS_IsMainThread(),
279 mCurrentEventTarget
== GetMainThreadSerialEventTarget());
282 mCurrentEventTarget
== MessageLoop::current()->SerialEventTarget());
288 auto data
= mData
.Lock();
289 if (data
->mDestroyed
) {
295 // Check if next task could be handled on current thread
296 if (!data
->mTasks
.empty() &&
297 data
->mTasks
.front().second
== mCurrentEventTarget
) {
298 frontTask
= data
->mTasks
.front().first
;
303 MOZ_ASSERT(!frontTask
);
305 // Post tasks to different thread if pending tasks exist.
307 auto data
= mData
.Lock();
308 data
->mCurrentRunnable
= nullptr;
309 mCurrentEventTarget
= nullptr;
311 if (data
->mDestroyed
|| data
->mTasks
.empty()) {
315 PostTasksIfNecessary(data
->mTasks
.front().second
, data
);
320 StaticRefPtr
<WinWindowOcclusionTracker
> WinWindowOcclusionTracker::sTracker
;
323 WinWindowOcclusionTracker
* WinWindowOcclusionTracker::Get() {
324 MOZ_ASSERT(NS_IsMainThread());
325 if (!sTracker
|| sTracker
->mHasAttemptedShutdown
) {
332 void WinWindowOcclusionTracker::Ensure() {
333 MOZ_ASSERT(NS_IsMainThread());
334 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::Ensure()");
336 base::Thread::Options options
;
337 options
.message_loop_type
= MessageLoop::TYPE_UI
;
340 // Try to reuse the thread, which involves stopping and restarting it.
341 sTracker
->mThread
->Stop();
342 if (sTracker
->mThread
->StartWithOptions(options
)) {
344 sTracker
->mHasAttemptedShutdown
= false;
347 // Restart failed, so null out our sTracker and try again with a new
348 // thread. This will cause the old singleton instance to be deallocated,
349 // which will destroy its mThread as well.
353 UniquePtr
<base::Thread
> thread
=
354 MakeUnique
<base::Thread
>("WinWindowOcclusionCalc");
356 if (!thread
->StartWithOptions(options
)) {
360 sTracker
= new WinWindowOcclusionTracker(std::move(thread
));
361 WindowOcclusionCalculator::CreateInstance();
363 RefPtr
<Runnable
> runnable
=
364 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
365 WindowOcclusionCalculator::GetInstance()),
366 &WindowOcclusionCalculator::Initialize
);
367 sTracker
->mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
371 void WinWindowOcclusionTracker::ShutDown() {
376 MOZ_ASSERT(NS_IsMainThread());
377 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::ShutDown()");
379 sTracker
->mHasAttemptedShutdown
= true;
382 // Our thread could hang while we're waiting for it to stop.
383 // Since we're shutting down, that's not a critical problem.
384 // We set a reasonable amount of time to wait for shutdown,
385 // and if it succeeds within that time, we correctly stop
386 // our thread by nulling out the refptr, which will cause it
387 // to be deallocated and join the thread. If it times out,
388 // we do nothing, which means that the thread will not be
389 // joined and sTracker memory will leak.
392 // It's important to hold the lock before posting the
393 // runnable. This ensures that the runnable can't begin
394 // until we've started our Wait, which prevents us from
395 // Waiting on a monitor that has already been notified.
396 MonitorAutoLock
lock(sTracker
->mMonitor
);
398 static const TimeDuration TIMEOUT
= TimeDuration::FromSeconds(2.0);
399 RefPtr
<Runnable
> runnable
=
400 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
401 WindowOcclusionCalculator::GetInstance()),
402 &WindowOcclusionCalculator::Shutdown
);
403 OcclusionCalculatorLoop()->PostTask(runnable
.forget());
405 // Monitor uses SleepConditionVariableSRW, which can have
406 // spurious wakeups which are reported as timeouts, so we
407 // check timestamps to ensure that we've waited as long we
408 // intended to. If we wake early, we don't bother calculating
409 // a precise amount for the next wait; we just wait the same
410 // amount of time. This means timeout might happen after as
411 // much as 2x the TIMEOUT time.
412 TimeStamp timeStart
= TimeStamp::NowLoRes();
414 status
= sTracker
->mMonitor
.Wait(TIMEOUT
);
415 } while ((status
== CVStatus::Timeout
) &&
416 ((TimeStamp::NowLoRes() - timeStart
) < TIMEOUT
));
419 if (status
== CVStatus::NoTimeout
) {
420 WindowOcclusionCalculator::ClearInstance();
425 void WinWindowOcclusionTracker::Destroy() {
426 if (mSerializedTaskDispatcher
) {
427 mSerializedTaskDispatcher
->Destroy();
432 MessageLoop
* WinWindowOcclusionTracker::OcclusionCalculatorLoop() {
433 return sTracker
? sTracker
->mThread
->message_loop() : nullptr;
437 bool WinWindowOcclusionTracker::IsInWinWindowOcclusionThread() {
439 sTracker
->mThread
->thread_id() == PlatformThread::CurrentId();
442 void WinWindowOcclusionTracker::Enable(nsBaseWidget
* aWindow
, HWND aHwnd
) {
443 MOZ_ASSERT(NS_IsMainThread());
444 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::Enable() aWindow %p aHwnd %p",
447 auto it
= mHwndRootWindowMap
.find(aHwnd
);
448 if (it
!= mHwndRootWindowMap
.end()) {
452 nsWeakPtr weak
= do_GetWeakReference(aWindow
);
453 mHwndRootWindowMap
.emplace(aHwnd
, weak
);
455 RefPtr
<Runnable
> runnable
= WrapRunnable(
456 RefPtr
<WindowOcclusionCalculator
>(
457 WindowOcclusionCalculator::GetInstance()),
458 &WindowOcclusionCalculator::EnableOcclusionTrackingForWindow
, aHwnd
);
459 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
462 void WinWindowOcclusionTracker::Disable(nsBaseWidget
* aWindow
, HWND aHwnd
) {
463 MOZ_ASSERT(NS_IsMainThread());
465 "WinWindowOcclusionTracker::Disable() aWindow %p aHwnd %p", aWindow
,
468 auto it
= mHwndRootWindowMap
.find(aHwnd
);
469 if (it
== mHwndRootWindowMap
.end()) {
473 mHwndRootWindowMap
.erase(it
);
475 RefPtr
<Runnable
> runnable
= WrapRunnable(
476 RefPtr
<WindowOcclusionCalculator
>(
477 WindowOcclusionCalculator::GetInstance()),
478 &WindowOcclusionCalculator::DisableOcclusionTrackingForWindow
, aHwnd
);
479 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
482 void WinWindowOcclusionTracker::OnWindowVisibilityChanged(nsBaseWidget
* aWindow
,
484 MOZ_ASSERT(NS_IsMainThread());
486 "WinWindowOcclusionTracker::OnWindowVisibilityChanged() aWindow %p "
490 RefPtr
<Runnable
> runnable
= WrapRunnable(
491 RefPtr
<WindowOcclusionCalculator
>(
492 WindowOcclusionCalculator::GetInstance()),
493 &WindowOcclusionCalculator::HandleVisibilityChanged
, aVisible
);
494 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
497 WinWindowOcclusionTracker::WinWindowOcclusionTracker(
498 UniquePtr
<base::Thread
> aThread
)
499 : mThread(std::move(aThread
)), mMonitor("WinWindowOcclusionTracker") {
500 MOZ_ASSERT(NS_IsMainThread());
501 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::WinWindowOcclusionTracker()");
503 WinEventWindow::Ensure();
505 mSerializedTaskDispatcher
= new SerializedTaskDispatcher();
508 WinWindowOcclusionTracker::~WinWindowOcclusionTracker() {
509 MOZ_ASSERT(NS_IsMainThread());
511 "WinWindowOcclusionTracker::~WinWindowOcclusionTracker()");
515 bool WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
516 HWND aHwnd
, LayoutDeviceIntRect
* aWindowRect
) {
517 // Filter out windows that are not "visible", IsWindowVisible().
518 if (!::IsWindow(aHwnd
) || !::IsWindowVisible(aHwnd
)) {
522 // Filter out minimized windows.
523 if (::IsIconic(aHwnd
)) {
527 LONG exStyles
= ::GetWindowLong(aHwnd
, GWL_EXSTYLE
);
528 // Filter out "transparent" windows, windows where the mouse clicks fall
530 if (exStyles
& WS_EX_TRANSPARENT
) {
534 // Filter out "tool windows", which are floating windows that do not appear on
535 // the taskbar or ALT-TAB. Floating windows can have larger window rectangles
536 // than what is visible to the user, so by filtering them out we will avoid
537 // incorrectly marking native windows as occluded. We do not filter out the
539 if (exStyles
& WS_EX_TOOLWINDOW
) {
540 nsAutoString className
;
541 if (WinUtils::GetClassName(aHwnd
, className
)) {
542 if (!className
.Equals(L
"Shell_TrayWnd")) {
548 // Filter out layered windows that are not opaque or that set a transparency
550 if (exStyles
& WS_EX_LAYERED
) {
554 // GetLayeredWindowAttributes only works if the application has
555 // previously called SetLayeredWindowAttributes on the window.
556 // The function will fail if the layered window was setup with
557 // UpdateLayeredWindow. Treat this failure as the window being transparent.
558 // See Remarks section of
559 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlayeredwindowattributes
560 if (!::GetLayeredWindowAttributes(aHwnd
, nullptr, &alpha
, &flags
)) {
564 if (flags
& LWA_ALPHA
&& alpha
< 255) {
567 if (flags
& LWA_COLORKEY
) {
572 // Filter out windows that do not have a simple rectangular region.
573 HRGN region
= ::CreateRectRgn(0, 0, 0, 0);
574 int result
= GetWindowRgn(aHwnd
, region
);
575 ::DeleteObject(region
);
576 if (result
== COMPLEXREGION
) {
580 // Windows 10 has cloaked windows, windows with WS_VISIBLE attribute but
581 // not displayed. explorer.exe, in particular has one that's the
582 // size of the desktop. It's usually behind Chrome windows in the z-order,
583 // but using a remote desktop can move it up in the z-order. So, ignore them.
585 if (SUCCEEDED(::DwmGetWindowAttribute(aHwnd
, DWMWA_CLOAKED
, &reason
,
592 // Filter out windows that take up zero area. The call to GetWindowRect is one
593 // of the most expensive parts of this function, so it is last.
594 if (!::GetWindowRect(aHwnd
, &winRect
)) {
597 if (::IsRectEmpty(&winRect
)) {
601 // Ignore popup windows since they're transient unless it is the Windows
603 // XXX Chrome Widget popup handling is removed for now.
604 if (::GetWindowLong(aHwnd
, GWL_STYLE
) & WS_POPUP
) {
605 nsAutoString className
;
606 if (WinUtils::GetClassName(aHwnd
, className
)) {
607 if (!className
.Equals(L
"Shell_TrayWnd")) {
613 *aWindowRect
= LayoutDeviceIntRect(winRect
.left
, winRect
.top
,
614 winRect
.right
- winRect
.left
,
615 winRect
.bottom
- winRect
.top
);
617 WINDOWPLACEMENT windowPlacement
= {0};
618 windowPlacement
.length
= sizeof(WINDOWPLACEMENT
);
619 ::GetWindowPlacement(aHwnd
, &windowPlacement
);
620 if (windowPlacement
.showCmd
== SW_MAXIMIZE
) {
621 // If the window is maximized the window border extends beyond the visible
622 // region of the screen. Adjust the maximized window rect to fit the
623 // screen dimensions to ensure that fullscreen windows, which do not extend
624 // beyond the screen boundaries since they typically have no borders, will
625 // occlude maximized windows underneath them.
626 HMONITOR hmon
= ::MonitorFromWindow(aHwnd
, MONITOR_DEFAULTTONEAREST
);
629 mi
.cbSize
= sizeof(mi
);
630 if (GetMonitorInfo(hmon
, &mi
)) {
631 LayoutDeviceIntRect
workArea(mi
.rcWork
.left
, mi
.rcWork
.top
,
632 mi
.rcWork
.right
- mi
.rcWork
.left
,
633 mi
.rcWork
.bottom
- mi
.rcWork
.top
);
634 // Adjust aWindowRect to fit to monitor.
635 aWindowRect
->width
= std::min(workArea
.width
, aWindowRect
->width
);
636 if (aWindowRect
->x
< workArea
.x
) {
637 aWindowRect
->x
= workArea
.x
;
639 aWindowRect
->x
= std::min(workArea
.x
+ workArea
.width
,
640 aWindowRect
->x
+ aWindowRect
->width
) -
643 aWindowRect
->height
= std::min(workArea
.height
, aWindowRect
->height
);
644 if (aWindowRect
->y
< workArea
.y
) {
645 aWindowRect
->y
= workArea
.y
;
647 aWindowRect
->y
= std::min(workArea
.y
+ workArea
.height
,
648 aWindowRect
->y
+ aWindowRect
->height
) -
659 void WinWindowOcclusionTracker::CallUpdateOcclusionState(
660 std::unordered_map
<HWND
, OcclusionState
>* aMap
, bool aShowAllWindows
) {
661 MOZ_ASSERT(NS_IsMainThread());
663 auto* tracker
= WinWindowOcclusionTracker::Get();
667 tracker
->UpdateOcclusionState(aMap
, aShowAllWindows
);
670 void WinWindowOcclusionTracker::UpdateOcclusionState(
671 std::unordered_map
<HWND
, OcclusionState
>* aMap
, bool aShowAllWindows
) {
672 MOZ_ASSERT(NS_IsMainThread());
673 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
675 "WinWindowOcclusionTracker::UpdateOcclusionState() aShowAllWindows %d",
678 mNumVisibleRootWindows
= 0;
679 for (auto& [hwnd
, state
] : *aMap
) {
680 auto it
= mHwndRootWindowMap
.find(hwnd
);
681 // The window was destroyed while processing occlusion.
682 if (it
== mHwndRootWindowMap
.end()) {
685 auto occlState
= state
;
687 // If the screen is locked or off, ignore occlusion state results and
688 // mark the window as occluded.
689 if (mScreenLocked
|| !mDisplayOn
) {
690 occlState
= OcclusionState::OCCLUDED
;
691 } else if (aShowAllWindows
) {
692 occlState
= OcclusionState::VISIBLE
;
694 nsCOMPtr
<nsIWidget
> widget
= do_QueryReferent(it
->second
);
698 auto* baseWidget
= static_cast<nsBaseWidget
*>(widget
.get());
699 baseWidget
->NotifyOcclusionState(occlState
);
700 if (baseWidget
->SizeMode() != nsSizeMode_Minimized
) {
701 mNumVisibleRootWindows
++;
706 void WinWindowOcclusionTracker::OnSessionChange(WPARAM aStatusCode
) {
707 MOZ_ASSERT(NS_IsMainThread());
709 widget_windows_window_occlusion_tracking_session_lock_enabled()) {
713 if (aStatusCode
== WTS_SESSION_UNLOCK
) {
715 "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_UNLOCK");
717 // UNLOCK will cause a foreground window change, which will
718 // trigger an occlusion calculation on its own.
719 mScreenLocked
= false;
720 } else if (aStatusCode
== WTS_SESSION_LOCK
) {
722 "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_LOCK");
724 mScreenLocked
= true;
725 MarkNonIconicWindowsOccluded();
729 void WinWindowOcclusionTracker::OnDisplayStateChanged(bool aDisplayOn
) {
730 MOZ_ASSERT(NS_IsMainThread());
732 widget_windows_window_occlusion_tracking_display_state_enabled()) {
737 "WinWindowOcclusionTracker::OnDisplayStateChanged() aDisplayOn %d",
740 if (mDisplayOn
== aDisplayOn
) {
744 mDisplayOn
= aDisplayOn
;
746 // Notify the window occlusion calculator of the display turning on
747 // which will schedule an occlusion calculation. This must be run
748 // on the WindowOcclusionCalculator thread.
749 RefPtr
<Runnable
> runnable
=
750 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
751 WindowOcclusionCalculator::GetInstance()),
752 &WindowOcclusionCalculator::HandleVisibilityChanged
,
753 /* aVisible */ true);
754 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
756 MarkNonIconicWindowsOccluded();
760 void WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded() {
761 MOZ_ASSERT(NS_IsMainThread());
763 "WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded()");
765 // Set all visible root windows as occluded. If not visible,
766 // set them as hidden.
767 for (auto& [hwnd
, weak
] : mHwndRootWindowMap
) {
768 nsCOMPtr
<nsIWidget
> widget
= do_QueryReferent(weak
);
772 auto* baseWidget
= static_cast<nsBaseWidget
*>(widget
.get());
773 auto state
= (baseWidget
->SizeMode() == nsSizeMode_Minimized
)
774 ? OcclusionState::HIDDEN
775 : OcclusionState::OCCLUDED
;
776 baseWidget
->NotifyOcclusionState(state
);
780 void WinWindowOcclusionTracker::TriggerCalculation() {
781 RefPtr
<Runnable
> runnable
=
782 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
783 WindowOcclusionCalculator::GetInstance()),
784 &WindowOcclusionCalculator::HandleTriggerCalculation
);
785 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
789 BOOL
WinWindowOcclusionTracker::DumpOccludingWindowsCallback(HWND aHWnd
,
791 HWND hwnd
= reinterpret_cast<HWND
>(aLParam
);
793 LayoutDeviceIntRect windowRect
;
794 bool windowIsOccluding
= IsWindowVisibleAndFullyOpaque(aHWnd
, &windowRect
);
795 if (windowIsOccluding
) {
796 nsAutoString className
;
797 if (WinUtils::GetClassName(aHWnd
, className
)) {
798 const auto name
= NS_ConvertUTF16toUTF8(className
);
800 "DumpOccludingWindowsCallback() aHWnd %p className %s windowRect(%d, "
802 aHWnd
, name
.get(), windowRect
.x
, windowRect
.y
, windowRect
.width
,
813 void WinWindowOcclusionTracker::DumpOccludingWindows(HWND aHWnd
) {
814 printf_stderr("DumpOccludingWindows() until aHWnd %p visible %d iconic %d\n",
815 aHWnd
, ::IsWindowVisible(aHWnd
), ::IsIconic(aHWnd
));
816 ::EnumWindows(&DumpOccludingWindowsCallback
, reinterpret_cast<LPARAM
>(aHWnd
));
820 StaticRefPtr
<WinWindowOcclusionTracker::WindowOcclusionCalculator
>
821 WinWindowOcclusionTracker::WindowOcclusionCalculator::sCalculator
;
823 WinWindowOcclusionTracker::WindowOcclusionCalculator::
824 WindowOcclusionCalculator()
825 : mMonitor(WinWindowOcclusionTracker::Get()->mMonitor
) {
826 MOZ_ASSERT(NS_IsMainThread());
827 LOG(LogLevel::Info
, "WindowOcclusionCalculator()");
829 mSerializedTaskDispatcher
=
830 WinWindowOcclusionTracker::Get()->GetSerializedTaskDispatcher();
833 WinWindowOcclusionTracker::WindowOcclusionCalculator::
834 ~WindowOcclusionCalculator() {}
837 void WinWindowOcclusionTracker::WindowOcclusionCalculator::CreateInstance() {
838 MOZ_ASSERT(NS_IsMainThread());
839 sCalculator
= new WindowOcclusionCalculator();
843 void WinWindowOcclusionTracker::WindowOcclusionCalculator::ClearInstance() {
844 MOZ_ASSERT(NS_IsMainThread());
845 sCalculator
= nullptr;
848 void WinWindowOcclusionTracker::WindowOcclusionCalculator::Initialize() {
849 MOZ_ASSERT(IsInWinWindowOcclusionThread());
850 MOZ_ASSERT(!mVirtualDesktopManager
);
851 CALC_LOG(LogLevel::Info
, "Initialize()");
853 RefPtr
<IVirtualDesktopManager
> desktopManager
;
854 HRESULT hr
= ::CoCreateInstance(
855 CLSID_VirtualDesktopManager
, NULL
, CLSCTX_INPROC_SERVER
,
856 __uuidof(IVirtualDesktopManager
), getter_AddRefs(desktopManager
));
860 mVirtualDesktopManager
= desktopManager
;
863 void WinWindowOcclusionTracker::WindowOcclusionCalculator::Shutdown() {
864 MonitorAutoLock
lock(mMonitor
);
866 MOZ_ASSERT(IsInWinWindowOcclusionThread());
867 CALC_LOG(LogLevel::Info
, "Shutdown()");
869 UnregisterEventHooks();
870 if (mOcclusionUpdateRunnable
) {
871 mOcclusionUpdateRunnable
->Cancel();
872 mOcclusionUpdateRunnable
= nullptr;
874 mVirtualDesktopManager
= nullptr;
876 mMonitor
.NotifyAll();
879 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
880 EnableOcclusionTrackingForWindow(HWND aHwnd
) {
881 MOZ_ASSERT(IsInWinWindowOcclusionThread());
882 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
883 CALC_LOG(LogLevel::Info
, "EnableOcclusionTrackingForWindow() aHwnd %p",
886 MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState
.find(aHwnd
) ==
887 mRootWindowHwndsOcclusionState
.end());
888 mRootWindowHwndsOcclusionState
[aHwnd
] = OcclusionState::UNKNOWN
;
890 if (mGlobalEventHooks
.empty()) {
891 RegisterEventHooks();
894 // Schedule an occlusion calculation so that the newly tracked window does
895 // not have a stale occlusion status.
896 ScheduleOcclusionCalculationIfNeeded();
899 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
900 DisableOcclusionTrackingForWindow(HWND aHwnd
) {
901 MOZ_ASSERT(IsInWinWindowOcclusionThread());
902 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
903 CALC_LOG(LogLevel::Info
, "DisableOcclusionTrackingForWindow() aHwnd %p",
906 MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState
.find(aHwnd
) !=
907 mRootWindowHwndsOcclusionState
.end());
908 mRootWindowHwndsOcclusionState
.erase(aHwnd
);
910 if (mMovingWindow
== aHwnd
) {
914 if (mRootWindowHwndsOcclusionState
.empty()) {
915 UnregisterEventHooks();
916 if (mOcclusionUpdateRunnable
) {
917 mOcclusionUpdateRunnable
->Cancel();
918 mOcclusionUpdateRunnable
= nullptr;
923 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
924 HandleVisibilityChanged(bool aVisible
) {
925 MOZ_ASSERT(IsInWinWindowOcclusionThread());
926 CALC_LOG(LogLevel::Info
, "HandleVisibilityChange() aVisible %d", aVisible
);
928 // May have gone from having no visible windows to having one, in
929 // which case we need to register event hooks, and make sure that an
930 // occlusion calculation is scheduled.
932 MaybeRegisterEventHooks();
933 ScheduleOcclusionCalculationIfNeeded();
937 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
938 HandleTriggerCalculation() {
939 MOZ_ASSERT(IsInWinWindowOcclusionThread());
940 CALC_LOG(LogLevel::Info
, "HandleTriggerCalculation()");
942 MaybeRegisterEventHooks();
943 ScheduleOcclusionCalculationIfNeeded();
946 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
947 MaybeRegisterEventHooks() {
948 if (mGlobalEventHooks
.empty()) {
949 RegisterEventHooks();
955 WinWindowOcclusionTracker::WindowOcclusionCalculator::EventHookCallback(
956 HWINEVENTHOOK aWinEventHook
, DWORD aEvent
, HWND aHwnd
, LONG aIdObject
,
957 LONG aIdChild
, DWORD aEventThread
, DWORD aMsEventTime
) {
959 sCalculator
->ProcessEventHookCallback(aWinEventHook
, aEvent
, aHwnd
,
960 aIdObject
, aIdChild
);
965 BOOL CALLBACK
WinWindowOcclusionTracker::WindowOcclusionCalculator::
966 ComputeNativeWindowOcclusionStatusCallback(HWND aHwnd
, LPARAM aLParam
) {
968 return sCalculator
->ProcessComputeNativeWindowOcclusionStatusCallback(
969 aHwnd
, reinterpret_cast<std::unordered_set
<DWORD
>*>(aLParam
));
975 BOOL CALLBACK
WinWindowOcclusionTracker::WindowOcclusionCalculator::
976 UpdateVisibleWindowProcessIdsCallback(HWND aHwnd
, LPARAM aLParam
) {
978 sCalculator
->ProcessUpdateVisibleWindowProcessIdsCallback(aHwnd
);
984 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
985 UpdateVisibleWindowProcessIds() {
986 mPidsForLocationChangeHook
.clear();
987 ::EnumWindows(&UpdateVisibleWindowProcessIdsCallback
, 0);
990 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
991 ComputeNativeWindowOcclusionStatus() {
992 MOZ_ASSERT(IsInWinWindowOcclusionThread());
993 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
995 if (mOcclusionUpdateRunnable
) {
996 mOcclusionUpdateRunnable
= nullptr;
999 if (mRootWindowHwndsOcclusionState
.empty()) {
1003 // Set up initial conditions for occlusion calculation.
1004 bool shouldUnregisterEventHooks
= true;
1006 // Compute the LayoutDeviceIntRegion for the screen.
1007 int screenLeft
= ::GetSystemMetrics(SM_XVIRTUALSCREEN
);
1008 int screenTop
= ::GetSystemMetrics(SM_YVIRTUALSCREEN
);
1009 int screenWidth
= ::GetSystemMetrics(SM_CXVIRTUALSCREEN
);
1010 int screenHeight
= ::GetSystemMetrics(SM_CYVIRTUALSCREEN
);
1011 LayoutDeviceIntRegion screenRegion
=
1012 LayoutDeviceIntRect(screenLeft
, screenTop
, screenWidth
, screenHeight
);
1013 mNumRootWindowsWithUnknownOcclusionState
= 0;
1015 CALC_LOG(LogLevel::Debug
,
1016 "ComputeNativeWindowOcclusionStatus() screen(%d, %d, %d, %d)",
1017 screenLeft
, screenTop
, screenWidth
, screenHeight
);
1019 for (auto& [hwnd
, state
] : mRootWindowHwndsOcclusionState
) {
1020 // IsIconic() checks for a minimized window. Immediately set the state of
1021 // minimized windows to HIDDEN.
1022 if (::IsIconic(hwnd
)) {
1023 state
= OcclusionState::HIDDEN
;
1024 } else if (IsWindowOnCurrentVirtualDesktop(hwnd
) == Some(false)) {
1025 // If window is not on the current virtual desktop, immediately
1026 // set the state of the window to OCCLUDED.
1027 state
= OcclusionState::OCCLUDED
;
1028 // Don't unregister event hooks when not on current desktop. There's no
1029 // notification when that changes, so we can't reregister event hooks.
1030 shouldUnregisterEventHooks
= false;
1032 state
= OcclusionState::UNKNOWN
;
1033 shouldUnregisterEventHooks
= false;
1034 mNumRootWindowsWithUnknownOcclusionState
++;
1038 // Unregister event hooks if all native windows are minimized.
1039 if (shouldUnregisterEventHooks
) {
1040 UnregisterEventHooks();
1042 std::unordered_set
<DWORD
> currentPidsWithVisibleWindows
;
1043 mUnoccludedDesktopRegion
= screenRegion
;
1044 // Calculate unoccluded region if there is a non-minimized native window.
1045 // Also compute |current_pids_with_visible_windows| as we enumerate
1047 EnumWindows(&ComputeNativeWindowOcclusionStatusCallback
,
1048 reinterpret_cast<LPARAM
>(¤tPidsWithVisibleWindows
));
1049 // Check if mPidsForLocationChangeHook has any pids of processes
1050 // currently without visible windows. If so, unhook the win event,
1051 // remove the pid from mPidsForLocationChangeHook and remove
1052 // the corresponding event hook from mProcessEventHooks.
1053 std::unordered_set
<DWORD
> pidsToRemove
;
1054 for (auto locChangePid
: mPidsForLocationChangeHook
) {
1055 if (currentPidsWithVisibleWindows
.find(locChangePid
) ==
1056 currentPidsWithVisibleWindows
.end()) {
1057 // Remove the event hook from our map, and unregister the event hook.
1058 // It's possible the eventhook will no longer be valid, but if we don't
1059 // unregister the event hook, a process that toggles between having
1060 // visible windows and not having visible windows could cause duplicate
1061 // event hooks to get registered for the process.
1062 UnhookWinEvent(mProcessEventHooks
[locChangePid
]);
1063 mProcessEventHooks
.erase(locChangePid
);
1064 pidsToRemove
.insert(locChangePid
);
1067 if (!pidsToRemove
.empty()) {
1069 for (auto it
= mPidsForLocationChangeHook
.begin();
1070 it
!= mPidsForLocationChangeHook
.end();) {
1071 if (pidsToRemove
.find(*it
) != pidsToRemove
.end()) {
1072 it
= mPidsForLocationChangeHook
.erase(it
);
1080 std::unordered_map
<HWND
, OcclusionState
>* map
=
1081 &mRootWindowHwndsOcclusionState
;
1082 bool showAllWindows
= mShowingThumbnails
;
1083 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
1084 "CallUpdateOcclusionState", [map
, showAllWindows
]() {
1085 WinWindowOcclusionTracker::CallUpdateOcclusionState(map
,
1088 mSerializedTaskDispatcher
->PostTaskToMain(runnable
.forget());
1091 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1092 ScheduleOcclusionCalculationIfNeeded() {
1093 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1095 // OcclusionUpdateRunnable is already queued.
1096 if (mOcclusionUpdateRunnable
) {
1100 CALC_LOG(LogLevel::Debug
, "ScheduleOcclusionCalculationIfNeeded()");
1102 RefPtr
<CancelableRunnable
> task
= new OcclusionUpdateRunnable(this);
1103 mOcclusionUpdateRunnable
= task
;
1104 mSerializedTaskDispatcher
->PostDelayedTaskToCalculator(
1105 task
.forget(), kOcclusionUpdateRunnableDelayMs
);
1108 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1109 RegisterGlobalEventHook(DWORD aEventMin
, DWORD aEventMax
) {
1110 HWINEVENTHOOK eventHook
=
1111 ::SetWinEventHook(aEventMin
, aEventMax
, nullptr, &EventHookCallback
, 0, 0,
1112 WINEVENT_OUTOFCONTEXT
);
1113 mGlobalEventHooks
.push_back(eventHook
);
1116 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1117 RegisterEventHookForProcess(DWORD aPid
) {
1118 mPidsForLocationChangeHook
.insert(aPid
);
1119 mProcessEventHooks
[aPid
] = SetWinEventHook(
1120 EVENT_OBJECT_LOCATIONCHANGE
, EVENT_OBJECT_LOCATIONCHANGE
, nullptr,
1121 &EventHookCallback
, aPid
, 0, WINEVENT_OUTOFCONTEXT
);
1124 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1125 RegisterEventHooks() {
1126 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1127 MOZ_RELEASE_ASSERT(mGlobalEventHooks
.empty());
1128 CALC_LOG(LogLevel::Info
, "RegisterEventHooks()");
1130 // Detects native window lost mouse capture
1131 RegisterGlobalEventHook(EVENT_SYSTEM_CAPTUREEND
, EVENT_SYSTEM_CAPTUREEND
);
1133 // Detects native window move (drag) and resizing events.
1134 RegisterGlobalEventHook(EVENT_SYSTEM_MOVESIZESTART
, EVENT_SYSTEM_MOVESIZEEND
);
1136 // Detects native window minimize and restore from taskbar events.
1137 RegisterGlobalEventHook(EVENT_SYSTEM_MINIMIZESTART
, EVENT_SYSTEM_MINIMIZEEND
);
1139 // Detects foreground window changing.
1140 RegisterGlobalEventHook(EVENT_SYSTEM_FOREGROUND
, EVENT_SYSTEM_FOREGROUND
);
1142 // Detects objects getting shown and hidden. Used to know when the task bar
1143 // and alt tab are showing preview windows so we can unocclude windows.
1144 RegisterGlobalEventHook(EVENT_OBJECT_SHOW
, EVENT_OBJECT_HIDE
);
1146 // Detects object state changes, e.g., enable/disable state, native window
1147 // maximize and native window restore events.
1148 RegisterGlobalEventHook(EVENT_OBJECT_STATECHANGE
, EVENT_OBJECT_STATECHANGE
);
1150 // Cloaking and uncloaking of windows should trigger an occlusion calculation.
1151 // In particular, switching virtual desktops seems to generate these events.
1152 RegisterGlobalEventHook(EVENT_OBJECT_CLOAKED
, EVENT_OBJECT_UNCLOAKED
);
1154 // Determine which subset of processes to set EVENT_OBJECT_LOCATIONCHANGE on
1155 // because otherwise event throughput is very high, as it generates events
1156 // for location changes of all objects, including the mouse moving on top of a
1158 UpdateVisibleWindowProcessIds();
1159 for (DWORD pid
: mPidsForLocationChangeHook
) {
1160 RegisterEventHookForProcess(pid
);
1164 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1165 UnregisterEventHooks() {
1166 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1167 CALC_LOG(LogLevel::Info
, "UnregisterEventHooks()");
1169 for (const auto eventHook
: mGlobalEventHooks
) {
1170 ::UnhookWinEvent(eventHook
);
1172 mGlobalEventHooks
.clear();
1174 for (const auto& [pid
, eventHook
] : mProcessEventHooks
) {
1175 ::UnhookWinEvent(eventHook
);
1177 mProcessEventHooks
.clear();
1179 mPidsForLocationChangeHook
.clear();
1182 bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
1183 ProcessComputeNativeWindowOcclusionStatusCallback(
1184 HWND aHwnd
, std::unordered_set
<DWORD
>* aCurrentPidsWithVisibleWindows
) {
1185 LayoutDeviceIntRegion currUnoccludedDestkop
= mUnoccludedDesktopRegion
;
1186 LayoutDeviceIntRect windowRect
;
1187 bool windowIsOccluding
=
1188 WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd
, &windowRect
);
1189 if (windowIsOccluding
) {
1190 // Hook this window's process with EVENT_OBJECT_LOCATION_CHANGE, if we are
1191 // not already doing so.
1193 ::GetWindowThreadProcessId(aHwnd
, &pid
);
1194 aCurrentPidsWithVisibleWindows
->insert(pid
);
1195 auto it
= mProcessEventHooks
.find(pid
);
1196 if (it
== mProcessEventHooks
.end()) {
1197 RegisterEventHookForProcess(pid
);
1200 // If no more root windows to consider, return true so we can continue
1201 // looking for windows we haven't hooked.
1202 if (mNumRootWindowsWithUnknownOcclusionState
== 0) {
1206 mUnoccludedDesktopRegion
.SubOut(windowRect
);
1207 } else if (mNumRootWindowsWithUnknownOcclusionState
== 0) {
1208 // This window can't occlude other windows, but we've determined the
1209 // occlusion state of all root windows, so we can return.
1213 // Ignore moving windows when deciding if windows under it are occluded.
1214 if (aHwnd
== mMovingWindow
) {
1218 // Check if |hwnd| is a root window; if so, we're done figuring out
1219 // if it's occluded because we've seen all the windows "over" it.
1220 auto it
= mRootWindowHwndsOcclusionState
.find(aHwnd
);
1221 if (it
== mRootWindowHwndsOcclusionState
.end() ||
1222 it
->second
!= OcclusionState::UNKNOWN
) {
1226 CALC_LOG(LogLevel::Debug
,
1227 "ProcessComputeNativeWindowOcclusionStatusCallback() windowRect(%d, "
1228 "%d, %d, %d) IsOccluding %d",
1229 windowRect
.x
, windowRect
.y
, windowRect
.width
, windowRect
.height
,
1232 // On Win7, default theme makes root windows have complex regions by
1233 // default. But we can still check if their bounding rect is occluded.
1234 if (!windowIsOccluding
) {
1236 if (::GetWindowRect(aHwnd
, &rect
) != 0) {
1237 LayoutDeviceIntRect
windowRect(
1238 rect
.left
, rect
.top
, rect
.right
- rect
.left
, rect
.bottom
- rect
.top
);
1239 currUnoccludedDestkop
.SubOut(windowRect
);
1243 it
->second
= (mUnoccludedDesktopRegion
== currUnoccludedDestkop
)
1244 ? OcclusionState::OCCLUDED
1245 : OcclusionState::VISIBLE
;
1246 mNumRootWindowsWithUnknownOcclusionState
--;
1251 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1252 ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook
, DWORD aEvent
,
1253 HWND aHwnd
, LONG aIdObject
, LONG aIdChild
) {
1254 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1256 // No need to calculate occlusion if a zero HWND generated the event. This
1257 // happens if there is no window associated with the event, e.g., mouse move
1263 // We only care about events for window objects. In particular, we don't care
1264 // about OBJID_CARET, which is spammy.
1265 if (aIdObject
!= OBJID_WINDOW
) {
1269 CALC_LOG(LogLevel::Debug
,
1270 "WindowOcclusionCalculator::ProcessEventHookCallback() aEvent 0x%lx",
1273 // We generally ignore events for popup windows, except for when the taskbar
1274 // is hidden or Windows Taskbar, in which case we recalculate occlusion.
1275 // XXX Chrome Widget popup handling is removed for now.
1276 bool calculateOcclusion
= true;
1277 if (::GetWindowLong(aHwnd
, GWL_STYLE
) & WS_POPUP
) {
1278 nsAutoString className
;
1279 if (WinUtils::GetClassName(aHwnd
, className
)) {
1280 calculateOcclusion
= className
.Equals(L
"Shell_TrayWnd");
1284 // Detect if either the alt tab view or the task list thumbnail is being
1285 // shown. If so, mark all non-hidden windows as occluded, and remember that
1286 // we're in the showing_thumbnails state. This lasts until we get told that
1287 // either the alt tab view or task list thumbnail are hidden.
1288 if (aEvent
== EVENT_OBJECT_SHOW
) {
1289 // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
1291 if (mShowingThumbnails
) {
1294 nsAutoString className
;
1295 if (WinUtils::GetClassName(aHwnd
, className
)) {
1296 const auto name
= NS_ConvertUTF16toUTF8(className
);
1297 CALC_LOG(LogLevel::Debug
,
1298 "ProcessEventHookCallback() EVENT_OBJECT_SHOW %s", name
.get());
1300 if (name
.Equals("MultitaskingViewFrame") ||
1301 name
.Equals("TaskListThumbnailWnd")) {
1302 CALC_LOG(LogLevel::Info
,
1303 "ProcessEventHookCallback() mShowingThumbnails = true");
1304 mShowingThumbnails
= true;
1306 std::unordered_map
<HWND
, OcclusionState
>* map
=
1307 &mRootWindowHwndsOcclusionState
;
1308 bool showAllWindows
= mShowingThumbnails
;
1309 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
1310 "CallUpdateOcclusionState", [map
, showAllWindows
]() {
1311 WinWindowOcclusionTracker::CallUpdateOcclusionState(
1312 map
, showAllWindows
);
1314 mSerializedTaskDispatcher
->PostTaskToMain(runnable
.forget());
1318 } else if (aEvent
== EVENT_OBJECT_HIDE
) {
1319 // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
1321 if (!mShowingThumbnails
) {
1324 nsAutoString className
;
1325 WinUtils::GetClassName(aHwnd
, className
);
1326 const auto name
= NS_ConvertUTF16toUTF8(className
);
1327 CALC_LOG(LogLevel::Debug
, "ProcessEventHookCallback() EVENT_OBJECT_HIDE %s",
1329 if (name
.Equals("MultitaskingViewFrame") ||
1330 name
.Equals("TaskListThumbnailWnd")) {
1331 CALC_LOG(LogLevel::Info
,
1332 "ProcessEventHookCallback() mShowingThumbnails = false");
1333 mShowingThumbnails
= false;
1334 // Let occlusion calculation fix occlusion state, even though hwnd might
1335 // be a popup window.
1336 calculateOcclusion
= true;
1341 // Don't continually calculate occlusion while a window is moving (unless it's
1342 // a root window), but instead once at the beginning and once at the end.
1343 // Remember the window being moved so if it's a root window, we can ignore
1344 // it when deciding if windows under it are occluded.
1345 else if (aEvent
== EVENT_SYSTEM_MOVESIZESTART
) {
1346 mMovingWindow
= aHwnd
;
1347 } else if (aEvent
== EVENT_SYSTEM_MOVESIZEEND
) {
1349 } else if (mMovingWindow
!= 0) {
1350 if (aEvent
== EVENT_OBJECT_LOCATIONCHANGE
||
1351 aEvent
== EVENT_OBJECT_STATECHANGE
) {
1352 // Ignore move events if it's not a root window that's being moved. If it
1353 // is a root window, we want to calculate occlusion to support tab
1354 // dragging to windows that were occluded when the drag was started but
1355 // are no longer occluded.
1356 if (mRootWindowHwndsOcclusionState
.find(aHwnd
) ==
1357 mRootWindowHwndsOcclusionState
.end()) {
1361 // If we get an event that isn't a location/state change, then we probably
1362 // missed the movesizeend notification, or got events out of order. In
1363 // that case, we want to go back to normal occlusion calculation.
1368 if (!calculateOcclusion
) {
1372 ScheduleOcclusionCalculationIfNeeded();
1375 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1376 ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd
) {
1377 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1379 LayoutDeviceIntRect windowRect
;
1380 if (WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd
, &windowRect
)) {
1382 ::GetWindowThreadProcessId(aHwnd
, &pid
);
1383 mPidsForLocationChangeHook
.insert(pid
);
1387 bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
1388 WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
1389 HWND aHwnd
, LayoutDeviceIntRect
* aWindowRect
) {
1390 return IsWindowVisibleAndFullyOpaque(aHwnd
, aWindowRect
) &&
1391 (IsWindowOnCurrentVirtualDesktop(aHwnd
) == Some(true));
1394 Maybe
<bool> WinWindowOcclusionTracker::WindowOcclusionCalculator::
1395 IsWindowOnCurrentVirtualDesktop(HWND aHwnd
) {
1396 if (!mVirtualDesktopManager
) {
1400 BOOL onCurrentDesktop
;
1401 HRESULT hr
= mVirtualDesktopManager
->IsWindowOnCurrentVirtualDesktop(
1402 aHwnd
, &onCurrentDesktop
);
1404 // In this case, we do not know the window is in which virtual desktop.
1408 if (onCurrentDesktop
) {
1413 hr
= mVirtualDesktopManager
->GetWindowDesktopId(aHwnd
, &workspaceGuid
);
1415 // In this case, we do not know the window is in which virtual desktop.
1419 // IsWindowOnCurrentVirtualDesktop() is flaky for newly opened windows,
1420 // which causes test flakiness. Occasionally, it incorrectly says a window
1421 // is not on the current virtual desktop when it is. In this situation,
1422 // it also returns GUID_NULL for the desktop id.
1423 if (workspaceGuid
== GUID_NULL
) {
1424 // In this case, we do not know if the window is in which virtual desktop.
1425 // But we hanle it as on current virtual desktop.
1426 // It does not cause a problem to window occlusion.
1427 // Since if window is not on current virtual desktop, window size becomes
1428 // (0, 0, 0, 0). It makes window occlusion handling explicit. It is
1429 // necessary for gtest.
1439 } // namespace mozilla::widget