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"
30 namespace mozilla::widget
{
32 // Can be called on Main thread
33 LazyLogModule
gWinOcclusionTrackerLog("WinOcclusionTracker");
34 #define LOG(type, ...) MOZ_LOG(gWinOcclusionTrackerLog, type, (__VA_ARGS__))
36 // Can be called on OcclusionCalculator thread
37 LazyLogModule
gWinOcclusionCalculatorLog("WinOcclusionCalculator");
38 #define CALC_LOG(type, ...) \
39 MOZ_LOG(gWinOcclusionCalculatorLog, type, (__VA_ARGS__))
41 // ~16 ms = time between frames when frame rate is 60 FPS.
42 const int kOcclusionUpdateRunnableDelayMs
= 16;
44 class OcclusionUpdateRunnable
: public CancelableRunnable
{
46 explicit OcclusionUpdateRunnable(
47 WinWindowOcclusionTracker::WindowOcclusionCalculator
*
49 : CancelableRunnable("OcclusionUpdateRunnable"),
50 mOcclusionCalculator(aOcclusionCalculator
) {
51 mTimeStamp
= TimeStamp::Now();
54 NS_IMETHOD
Run() override
{
58 MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
61 round((TimeStamp::Now() - mTimeStamp
).ToMilliseconds());
62 CALC_LOG(LogLevel::Debug
,
63 "ComputeNativeWindowOcclusionStatus() latencyMs %u", latencyMs
);
65 mOcclusionCalculator
->ComputeNativeWindowOcclusionStatus();
69 nsresult
Cancel() override
{
71 mOcclusionCalculator
= nullptr;
76 bool mIsCanceled
= false;
77 RefPtr
<WinWindowOcclusionTracker::WindowOcclusionCalculator
>
82 // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
83 class SerializedTaskDispatcher
{
84 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SerializedTaskDispatcher
)
87 SerializedTaskDispatcher();
90 void PostTaskToMain(already_AddRefed
<nsIRunnable
> aTask
);
91 void PostTaskToCalculator(already_AddRefed
<nsIRunnable
> aTask
);
92 void PostDelayedTaskToCalculator(already_AddRefed
<Runnable
> aTask
,
94 bool IsOnCurrentThread();
97 friend class DelayedTaskRunnable
;
99 ~SerializedTaskDispatcher();
102 std::queue
<std::pair
<RefPtr
<nsIRunnable
>, RefPtr
<nsISerialEventTarget
>>>
104 bool mDestroyed
= false;
105 RefPtr
<Runnable
> mCurrentRunnable
;
108 void PostTasksIfNecessary(nsISerialEventTarget
* aEventTarget
,
109 const DataMutex
<Data
>::AutoLock
& aProofOfLock
);
110 void HandleDelayedTask(already_AddRefed
<nsIRunnable
> aTask
);
113 // Hold current EventTarget during calling nsIRunnable::Run().
114 RefPtr
<nsISerialEventTarget
> mCurrentEventTarget
= nullptr;
116 DataMutex
<Data
> mData
;
119 class DelayedTaskRunnable
: public Runnable
{
121 DelayedTaskRunnable(SerializedTaskDispatcher
* aSerializedTaskDispatcher
,
122 already_AddRefed
<Runnable
> aTask
)
123 : Runnable("DelayedTaskRunnable"),
124 mSerializedTaskDispatcher(aSerializedTaskDispatcher
),
127 NS_IMETHOD
Run() override
{
128 mSerializedTaskDispatcher
->HandleDelayedTask(mTask
.forget());
133 RefPtr
<SerializedTaskDispatcher
> mSerializedTaskDispatcher
;
134 RefPtr
<Runnable
> mTask
;
137 SerializedTaskDispatcher::SerializedTaskDispatcher()
138 : mData("SerializedTaskDispatcher::mData") {
139 MOZ_RELEASE_ASSERT(NS_IsMainThread());
141 "SerializedTaskDispatcher::SerializedTaskDispatcher() this %p", this);
144 SerializedTaskDispatcher::~SerializedTaskDispatcher() {
146 auto data
= mData
.Lock();
147 MOZ_ASSERT(data
->mDestroyed
);
148 MOZ_ASSERT(data
->mTasks
.empty());
152 void SerializedTaskDispatcher::Destroy() {
153 MOZ_RELEASE_ASSERT(NS_IsMainThread());
154 LOG(LogLevel::Info
, "SerializedTaskDispatcher::Destroy() this %p", this);
156 auto data
= mData
.Lock();
157 if (data
->mDestroyed
) {
161 data
->mDestroyed
= true;
162 std::queue
<std::pair
<RefPtr
<nsIRunnable
>, RefPtr
<nsISerialEventTarget
>>>
164 std::swap(data
->mTasks
, empty
);
167 void SerializedTaskDispatcher::PostTaskToMain(
168 already_AddRefed
<nsIRunnable
> aTask
) {
169 RefPtr
<nsIRunnable
> task
= aTask
;
171 auto data
= mData
.Lock();
172 if (data
->mDestroyed
) {
176 nsISerialEventTarget
* eventTarget
= GetMainThreadSerialEventTarget();
177 data
->mTasks
.push({std::move(task
), eventTarget
});
179 MOZ_ASSERT_IF(!data
->mCurrentRunnable
, data
->mTasks
.size() == 1);
180 PostTasksIfNecessary(eventTarget
, data
);
183 void SerializedTaskDispatcher::PostTaskToCalculator(
184 already_AddRefed
<nsIRunnable
> aTask
) {
185 RefPtr
<nsIRunnable
> task
= aTask
;
187 auto data
= mData
.Lock();
188 if (data
->mDestroyed
) {
192 nsISerialEventTarget
* eventTarget
=
193 WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
194 data
->mTasks
.push({std::move(task
), eventTarget
});
196 MOZ_ASSERT_IF(!data
->mCurrentRunnable
, data
->mTasks
.size() == 1);
197 PostTasksIfNecessary(eventTarget
, data
);
200 void SerializedTaskDispatcher::PostDelayedTaskToCalculator(
201 already_AddRefed
<Runnable
> aTask
, int aDelayMs
) {
202 CALC_LOG(LogLevel::Debug
,
203 "SerializedTaskDispatcher::PostDelayedTaskToCalculator()");
205 RefPtr
<DelayedTaskRunnable
> runnable
=
206 new DelayedTaskRunnable(this, std::move(aTask
));
207 MessageLoop
* targetLoop
=
208 WinWindowOcclusionTracker::OcclusionCalculatorLoop();
209 targetLoop
->PostDelayedTask(runnable
.forget(), aDelayMs
);
212 bool SerializedTaskDispatcher::IsOnCurrentThread() {
213 return !!mCurrentEventTarget
;
216 void SerializedTaskDispatcher::PostTasksIfNecessary(
217 nsISerialEventTarget
* aEventTarget
,
218 const DataMutex
<Data
>::AutoLock
& aProofOfLock
) {
219 MOZ_ASSERT(!aProofOfLock
->mTasks
.empty());
221 if (aProofOfLock
->mCurrentRunnable
) {
225 RefPtr
<Runnable
> runnable
=
226 WrapRunnable(RefPtr
<SerializedTaskDispatcher
>(this),
227 &SerializedTaskDispatcher::HandleTasks
);
228 aProofOfLock
->mCurrentRunnable
= runnable
;
229 aEventTarget
->Dispatch(runnable
.forget());
232 void SerializedTaskDispatcher::HandleDelayedTask(
233 already_AddRefed
<nsIRunnable
> aTask
) {
234 MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
235 CALC_LOG(LogLevel::Debug
, "SerializedTaskDispatcher::HandleDelayedTask()");
237 RefPtr
<nsIRunnable
> task
= aTask
;
239 auto data
= mData
.Lock();
240 if (data
->mDestroyed
) {
244 nsISerialEventTarget
* eventTarget
=
245 WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
246 data
->mTasks
.push({std::move(task
), eventTarget
});
248 MOZ_ASSERT_IF(!data
->mCurrentRunnable
, data
->mTasks
.size() == 1);
249 PostTasksIfNecessary(eventTarget
, data
);
252 void SerializedTaskDispatcher::HandleTasks() {
253 RefPtr
<nsIRunnable
> frontTask
;
257 auto data
= mData
.Lock();
258 if (data
->mDestroyed
) {
261 MOZ_RELEASE_ASSERT(data
->mCurrentRunnable
);
262 MOZ_RELEASE_ASSERT(!data
->mTasks
.empty());
264 frontTask
= data
->mTasks
.front().first
;
266 MOZ_RELEASE_ASSERT(!mCurrentEventTarget
);
267 mCurrentEventTarget
= data
->mTasks
.front().second
;
271 if (NS_IsMainThread()) {
272 LOG(LogLevel::Debug
, "SerializedTaskDispatcher::HandleTasks()");
274 CALC_LOG(LogLevel::Debug
, "SerializedTaskDispatcher::HandleTasks()");
277 MOZ_ASSERT_IF(NS_IsMainThread(),
278 mCurrentEventTarget
== GetMainThreadSerialEventTarget());
281 mCurrentEventTarget
== MessageLoop::current()->SerialEventTarget());
287 auto data
= mData
.Lock();
288 if (data
->mDestroyed
) {
294 // Check if next task could be handled on current thread
295 if (!data
->mTasks
.empty() &&
296 data
->mTasks
.front().second
== mCurrentEventTarget
) {
297 frontTask
= data
->mTasks
.front().first
;
302 MOZ_ASSERT(!frontTask
);
304 // Post tasks to different thread if pending tasks exist.
306 auto data
= mData
.Lock();
307 data
->mCurrentRunnable
= nullptr;
308 mCurrentEventTarget
= nullptr;
310 if (data
->mDestroyed
|| data
->mTasks
.empty()) {
314 PostTasksIfNecessary(data
->mTasks
.front().second
, data
);
319 StaticRefPtr
<WinWindowOcclusionTracker
> WinWindowOcclusionTracker::sTracker
;
322 WinWindowOcclusionTracker
* WinWindowOcclusionTracker::Get() {
323 MOZ_ASSERT(NS_IsMainThread());
324 if (!sTracker
|| sTracker
->mHasAttemptedShutdown
) {
331 void WinWindowOcclusionTracker::Ensure() {
332 MOZ_ASSERT(NS_IsMainThread());
333 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::Ensure()");
335 base::Thread::Options options
;
336 options
.message_loop_type
= MessageLoop::TYPE_UI
;
339 // Try to reuse the thread, which involves stopping and restarting it.
340 sTracker
->mThread
->Stop();
341 if (sTracker
->mThread
->StartWithOptions(options
)) {
343 sTracker
->mHasAttemptedShutdown
= false;
345 // Take this opportunity to ensure that mDisplayStatusObserver and
346 // mSessionChangeObserver exist. They might have failed to be
347 // created when sTracker was created.
348 sTracker
->EnsureDisplayStatusObserver();
349 sTracker
->EnsureSessionChangeObserver();
352 // Restart failed, so null out our sTracker and try again with a new
353 // thread. This will cause the old singleton instance to be deallocated,
354 // which will destroy its mThread as well.
358 UniquePtr
<base::Thread
> thread
=
359 MakeUnique
<base::Thread
>("WinWindowOcclusionCalc");
361 if (!thread
->StartWithOptions(options
)) {
365 sTracker
= new WinWindowOcclusionTracker(std::move(thread
));
366 WindowOcclusionCalculator::CreateInstance();
368 RefPtr
<Runnable
> runnable
=
369 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
370 WindowOcclusionCalculator::GetInstance()),
371 &WindowOcclusionCalculator::Initialize
);
372 sTracker
->mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
376 void WinWindowOcclusionTracker::ShutDown() {
381 MOZ_ASSERT(NS_IsMainThread());
382 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::ShutDown()");
384 sTracker
->mHasAttemptedShutdown
= true;
387 // Our thread could hang while we're waiting for it to stop.
388 // Since we're shutting down, that's not a critical problem.
389 // We set a reasonable amount of time to wait for shutdown,
390 // and if it succeeds within that time, we correctly stop
391 // our thread by nulling out the refptr, which will cause it
392 // to be deallocated and join the thread. If it times out,
393 // we do nothing, which means that the thread will not be
394 // joined and sTracker memory will leak.
397 // It's important to hold the lock before posting the
398 // runnable. This ensures that the runnable can't begin
399 // until we've started our Wait, which prevents us from
400 // Waiting on a monitor that has already been notified.
401 MonitorAutoLock
lock(sTracker
->mMonitor
);
403 static const TimeDuration TIMEOUT
= TimeDuration::FromSeconds(2.0);
404 RefPtr
<Runnable
> runnable
=
405 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
406 WindowOcclusionCalculator::GetInstance()),
407 &WindowOcclusionCalculator::Shutdown
);
408 OcclusionCalculatorLoop()->PostTask(runnable
.forget());
410 // Monitor uses SleepConditionVariableSRW, which can have
411 // spurious wakeups which are reported as timeouts, so we
412 // check timestamps to ensure that we've waited as long we
413 // intended to. If we wake early, we don't bother calculating
414 // a precise amount for the next wait; we just wait the same
415 // amount of time. This means timeout might happen after as
416 // much as 2x the TIMEOUT time.
417 TimeStamp timeStart
= TimeStamp::NowLoRes();
419 status
= sTracker
->mMonitor
.Wait(TIMEOUT
);
420 } while ((status
== CVStatus::Timeout
) &&
421 ((TimeStamp::NowLoRes() - timeStart
) < TIMEOUT
));
424 if (status
== CVStatus::NoTimeout
) {
425 WindowOcclusionCalculator::ClearInstance();
430 void WinWindowOcclusionTracker::Destroy() {
431 if (mDisplayStatusObserver
) {
432 mDisplayStatusObserver
->Destroy();
433 mDisplayStatusObserver
= nullptr;
435 if (mSessionChangeObserver
) {
436 mSessionChangeObserver
->Destroy();
437 mSessionChangeObserver
= nullptr;
439 if (mSerializedTaskDispatcher
) {
440 mSerializedTaskDispatcher
->Destroy();
445 MessageLoop
* WinWindowOcclusionTracker::OcclusionCalculatorLoop() {
446 return sTracker
? sTracker
->mThread
->message_loop() : nullptr;
450 bool WinWindowOcclusionTracker::IsInWinWindowOcclusionThread() {
452 sTracker
->mThread
->thread_id() == PlatformThread::CurrentId();
455 void WinWindowOcclusionTracker::EnsureDisplayStatusObserver() {
456 if (mDisplayStatusObserver
) {
460 widget_windows_window_occlusion_tracking_display_state_enabled()) {
461 mDisplayStatusObserver
= DisplayStatusObserver::Create(this);
465 void WinWindowOcclusionTracker::EnsureSessionChangeObserver() {
466 if (mSessionChangeObserver
) {
470 widget_windows_window_occlusion_tracking_session_lock_enabled()) {
471 mSessionChangeObserver
= SessionChangeObserver::Create(this);
475 void WinWindowOcclusionTracker::Enable(nsBaseWidget
* aWindow
, HWND aHwnd
) {
476 MOZ_ASSERT(NS_IsMainThread());
477 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::Enable() aWindow %p aHwnd %p",
480 auto it
= mHwndRootWindowMap
.find(aHwnd
);
481 if (it
!= mHwndRootWindowMap
.end()) {
485 nsWeakPtr weak
= do_GetWeakReference(aWindow
);
486 mHwndRootWindowMap
.emplace(aHwnd
, weak
);
488 RefPtr
<Runnable
> runnable
= WrapRunnable(
489 RefPtr
<WindowOcclusionCalculator
>(
490 WindowOcclusionCalculator::GetInstance()),
491 &WindowOcclusionCalculator::EnableOcclusionTrackingForWindow
, aHwnd
);
492 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
495 void WinWindowOcclusionTracker::Disable(nsBaseWidget
* aWindow
, HWND aHwnd
) {
496 MOZ_ASSERT(NS_IsMainThread());
498 "WinWindowOcclusionTracker::Disable() aWindow %p aHwnd %p", aWindow
,
501 auto it
= mHwndRootWindowMap
.find(aHwnd
);
502 if (it
== mHwndRootWindowMap
.end()) {
506 mHwndRootWindowMap
.erase(it
);
508 RefPtr
<Runnable
> runnable
= WrapRunnable(
509 RefPtr
<WindowOcclusionCalculator
>(
510 WindowOcclusionCalculator::GetInstance()),
511 &WindowOcclusionCalculator::DisableOcclusionTrackingForWindow
, aHwnd
);
512 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
515 void WinWindowOcclusionTracker::OnWindowVisibilityChanged(nsBaseWidget
* aWindow
,
517 MOZ_ASSERT(NS_IsMainThread());
519 "WinWindowOcclusionTracker::OnWindowVisibilityChanged() aWindow %p "
523 RefPtr
<Runnable
> runnable
= WrapRunnable(
524 RefPtr
<WindowOcclusionCalculator
>(
525 WindowOcclusionCalculator::GetInstance()),
526 &WindowOcclusionCalculator::HandleVisibilityChanged
, aVisible
);
527 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
530 WinWindowOcclusionTracker::WinWindowOcclusionTracker(
531 UniquePtr
<base::Thread
> aThread
)
532 : mThread(std::move(aThread
)), mMonitor("WinWindowOcclusionTracker") {
533 MOZ_ASSERT(NS_IsMainThread());
534 LOG(LogLevel::Info
, "WinWindowOcclusionTracker::WinWindowOcclusionTracker()");
536 EnsureDisplayStatusObserver();
537 EnsureSessionChangeObserver();
539 mSerializedTaskDispatcher
= new SerializedTaskDispatcher();
542 WinWindowOcclusionTracker::~WinWindowOcclusionTracker() {
543 MOZ_ASSERT(NS_IsMainThread());
545 "WinWindowOcclusionTracker::~WinWindowOcclusionTracker()");
549 bool WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
550 HWND aHwnd
, LayoutDeviceIntRect
* aWindowRect
) {
551 // Filter out windows that are not "visible", IsWindowVisible().
552 if (!::IsWindow(aHwnd
) || !::IsWindowVisible(aHwnd
)) {
556 // Filter out minimized windows.
557 if (::IsIconic(aHwnd
)) {
561 LONG exStyles
= ::GetWindowLong(aHwnd
, GWL_EXSTYLE
);
562 // Filter out "transparent" windows, windows where the mouse clicks fall
564 if (exStyles
& WS_EX_TRANSPARENT
) {
568 // Filter out "tool windows", which are floating windows that do not appear on
569 // the taskbar or ALT-TAB. Floating windows can have larger window rectangles
570 // than what is visible to the user, so by filtering them out we will avoid
571 // incorrectly marking native windows as occluded. We do not filter out the
573 if (exStyles
& WS_EX_TOOLWINDOW
) {
574 nsAutoString className
;
575 if (WinUtils::GetClassName(aHwnd
, className
)) {
576 if (!className
.Equals(L
"Shell_TrayWnd")) {
582 // Filter out layered windows that are not opaque or that set a transparency
584 if (exStyles
& WS_EX_LAYERED
) {
588 // GetLayeredWindowAttributes only works if the application has
589 // previously called SetLayeredWindowAttributes on the window.
590 // The function will fail if the layered window was setup with
591 // UpdateLayeredWindow. Treat this failure as the window being transparent.
592 // See Remarks section of
593 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlayeredwindowattributes
594 if (!::GetLayeredWindowAttributes(aHwnd
, nullptr, &alpha
, &flags
)) {
598 if (flags
& LWA_ALPHA
&& alpha
< 255) {
601 if (flags
& LWA_COLORKEY
) {
606 // Filter out windows that do not have a simple rectangular region.
607 HRGN region
= ::CreateRectRgn(0, 0, 0, 0);
608 int result
= GetWindowRgn(aHwnd
, region
);
609 ::DeleteObject(region
);
610 if (result
== COMPLEXREGION
) {
614 // Windows 10 has cloaked windows, windows with WS_VISIBLE attribute but
615 // not displayed. explorer.exe, in particular has one that's the
616 // size of the desktop. It's usually behind Chrome windows in the z-order,
617 // but using a remote desktop can move it up in the z-order. So, ignore them.
619 if (SUCCEEDED(::DwmGetWindowAttribute(aHwnd
, DWMWA_CLOAKED
, &reason
,
626 // Filter out windows that take up zero area. The call to GetWindowRect is one
627 // of the most expensive parts of this function, so it is last.
628 if (!::GetWindowRect(aHwnd
, &winRect
)) {
631 if (::IsRectEmpty(&winRect
)) {
635 // Ignore popup windows since they're transient unless it is the Windows
637 // XXX Chrome Widget popup handling is removed for now.
638 if (::GetWindowLong(aHwnd
, GWL_STYLE
) & WS_POPUP
) {
639 nsAutoString className
;
640 if (WinUtils::GetClassName(aHwnd
, className
)) {
641 if (!className
.Equals(L
"Shell_TrayWnd")) {
647 *aWindowRect
= LayoutDeviceIntRect(winRect
.left
, winRect
.top
,
648 winRect
.right
- winRect
.left
,
649 winRect
.bottom
- winRect
.top
);
651 WINDOWPLACEMENT windowPlacement
= {0};
652 windowPlacement
.length
= sizeof(WINDOWPLACEMENT
);
653 ::GetWindowPlacement(aHwnd
, &windowPlacement
);
654 if (windowPlacement
.showCmd
== SW_MAXIMIZE
) {
655 // If the window is maximized the window border extends beyond the visible
656 // region of the screen. Adjust the maximized window rect to fit the
657 // screen dimensions to ensure that fullscreen windows, which do not extend
658 // beyond the screen boundaries since they typically have no borders, will
659 // occlude maximized windows underneath them.
660 HMONITOR hmon
= ::MonitorFromWindow(aHwnd
, MONITOR_DEFAULTTONEAREST
);
663 mi
.cbSize
= sizeof(mi
);
664 if (GetMonitorInfo(hmon
, &mi
)) {
665 LayoutDeviceIntRect
workArea(mi
.rcWork
.left
, mi
.rcWork
.top
,
666 mi
.rcWork
.right
- mi
.rcWork
.left
,
667 mi
.rcWork
.bottom
- mi
.rcWork
.top
);
668 // Adjust aWindowRect to fit to monitor.
669 aWindowRect
->width
= std::min(workArea
.width
, aWindowRect
->width
);
670 if (aWindowRect
->x
< workArea
.x
) {
671 aWindowRect
->x
= workArea
.x
;
673 aWindowRect
->x
= std::min(workArea
.x
+ workArea
.width
,
674 aWindowRect
->x
+ aWindowRect
->width
) -
677 aWindowRect
->height
= std::min(workArea
.height
, aWindowRect
->height
);
678 if (aWindowRect
->y
< workArea
.y
) {
679 aWindowRect
->y
= workArea
.y
;
681 aWindowRect
->y
= std::min(workArea
.y
+ workArea
.height
,
682 aWindowRect
->y
+ aWindowRect
->height
) -
693 void WinWindowOcclusionTracker::CallUpdateOcclusionState(
694 std::unordered_map
<HWND
, OcclusionState
>* aMap
, bool aShowAllWindows
) {
695 MOZ_ASSERT(NS_IsMainThread());
697 auto* tracker
= WinWindowOcclusionTracker::Get();
701 tracker
->UpdateOcclusionState(aMap
, aShowAllWindows
);
704 void WinWindowOcclusionTracker::UpdateOcclusionState(
705 std::unordered_map
<HWND
, OcclusionState
>* aMap
, bool aShowAllWindows
) {
706 MOZ_ASSERT(NS_IsMainThread());
707 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
709 "WinWindowOcclusionTracker::UpdateOcclusionState() aShowAllWindows %d",
712 mNumVisibleRootWindows
= 0;
713 for (auto& [hwnd
, state
] : *aMap
) {
714 auto it
= mHwndRootWindowMap
.find(hwnd
);
715 // The window was destroyed while processing occlusion.
716 if (it
== mHwndRootWindowMap
.end()) {
719 auto occlState
= state
;
721 // If the screen is locked or off, ignore occlusion state results and
722 // mark the window as occluded.
723 if (mScreenLocked
|| !mDisplayOn
) {
724 occlState
= OcclusionState::OCCLUDED
;
725 } else if (aShowAllWindows
) {
726 occlState
= OcclusionState::VISIBLE
;
728 nsCOMPtr
<nsIWidget
> widget
= do_QueryReferent(it
->second
);
732 auto* baseWidget
= static_cast<nsBaseWidget
*>(widget
.get());
733 baseWidget
->NotifyOcclusionState(occlState
);
734 if (baseWidget
->SizeMode() != nsSizeMode_Minimized
) {
735 mNumVisibleRootWindows
++;
740 void WinWindowOcclusionTracker::OnSessionChange(WPARAM aStatusCode
,
741 Maybe
<bool> aIsCurrentSession
) {
742 MOZ_ASSERT(NS_IsMainThread());
744 if (aIsCurrentSession
.isNothing() || !*aIsCurrentSession
) {
748 if (aStatusCode
== WTS_SESSION_UNLOCK
) {
750 "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_UNLOCK");
752 // UNLOCK will cause a foreground window change, which will
753 // trigger an occlusion calculation on its own.
754 mScreenLocked
= false;
755 } else if (aStatusCode
== WTS_SESSION_LOCK
) {
757 "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_LOCK");
759 mScreenLocked
= true;
760 MarkNonIconicWindowsOccluded();
764 void WinWindowOcclusionTracker::OnDisplayStateChanged(bool aDisplayOn
) {
765 MOZ_ASSERT(NS_IsMainThread());
767 "WinWindowOcclusionTracker::OnDisplayStateChanged() aDisplayOn %d",
770 if (mDisplayOn
== aDisplayOn
) {
774 mDisplayOn
= aDisplayOn
;
776 // Notify the window occlusion calculator of the display turning on
777 // which will schedule an occlusion calculation. This must be run
778 // on the WindowOcclusionCalculator thread.
779 RefPtr
<Runnable
> runnable
=
780 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
781 WindowOcclusionCalculator::GetInstance()),
782 &WindowOcclusionCalculator::HandleVisibilityChanged
,
783 /* aVisible */ true);
784 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
786 MarkNonIconicWindowsOccluded();
790 void WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded() {
791 MOZ_ASSERT(NS_IsMainThread());
793 "WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded()");
795 // Set all visible root windows as occluded. If not visible,
796 // set them as hidden.
797 for (auto& [hwnd
, weak
] : mHwndRootWindowMap
) {
798 nsCOMPtr
<nsIWidget
> widget
= do_QueryReferent(weak
);
802 auto* baseWidget
= static_cast<nsBaseWidget
*>(widget
.get());
803 auto state
= (baseWidget
->SizeMode() == nsSizeMode_Minimized
)
804 ? OcclusionState::HIDDEN
805 : OcclusionState::OCCLUDED
;
806 baseWidget
->NotifyOcclusionState(state
);
810 void WinWindowOcclusionTracker::TriggerCalculation() {
811 RefPtr
<Runnable
> runnable
=
812 WrapRunnable(RefPtr
<WindowOcclusionCalculator
>(
813 WindowOcclusionCalculator::GetInstance()),
814 &WindowOcclusionCalculator::HandleTriggerCalculation
);
815 mSerializedTaskDispatcher
->PostTaskToCalculator(runnable
.forget());
819 BOOL
WinWindowOcclusionTracker::DumpOccludingWindowsCallback(HWND aHWnd
,
821 HWND hwnd
= reinterpret_cast<HWND
>(aLParam
);
823 LayoutDeviceIntRect windowRect
;
824 bool windowIsOccluding
= IsWindowVisibleAndFullyOpaque(aHWnd
, &windowRect
);
825 if (windowIsOccluding
) {
826 nsAutoString className
;
827 if (WinUtils::GetClassName(aHWnd
, className
)) {
828 const auto name
= NS_ConvertUTF16toUTF8(className
);
830 "DumpOccludingWindowsCallback() aHWnd %p className %s windowRect(%d, "
832 aHWnd
, name
.get(), windowRect
.x
, windowRect
.y
, windowRect
.width
,
843 void WinWindowOcclusionTracker::DumpOccludingWindows(HWND aHWnd
) {
844 printf_stderr("DumpOccludingWindows() until aHWnd %p visible %d iconic %d\n",
845 aHWnd
, ::IsWindowVisible(aHWnd
), ::IsIconic(aHWnd
));
846 ::EnumWindows(&DumpOccludingWindowsCallback
, reinterpret_cast<LPARAM
>(aHWnd
));
850 StaticRefPtr
<WinWindowOcclusionTracker::WindowOcclusionCalculator
>
851 WinWindowOcclusionTracker::WindowOcclusionCalculator::sCalculator
;
853 WinWindowOcclusionTracker::WindowOcclusionCalculator::
854 WindowOcclusionCalculator()
855 : mMonitor(WinWindowOcclusionTracker::Get()->mMonitor
) {
856 MOZ_ASSERT(NS_IsMainThread());
857 LOG(LogLevel::Info
, "WindowOcclusionCalculator()");
859 mSerializedTaskDispatcher
=
860 WinWindowOcclusionTracker::Get()->GetSerializedTaskDispatcher();
863 WinWindowOcclusionTracker::WindowOcclusionCalculator::
864 ~WindowOcclusionCalculator() {}
867 void WinWindowOcclusionTracker::WindowOcclusionCalculator::CreateInstance() {
868 MOZ_ASSERT(NS_IsMainThread());
869 sCalculator
= new WindowOcclusionCalculator();
873 void WinWindowOcclusionTracker::WindowOcclusionCalculator::ClearInstance() {
874 MOZ_ASSERT(NS_IsMainThread());
875 sCalculator
= nullptr;
878 void WinWindowOcclusionTracker::WindowOcclusionCalculator::Initialize() {
879 MOZ_ASSERT(IsInWinWindowOcclusionThread());
880 MOZ_ASSERT(!mVirtualDesktopManager
);
881 CALC_LOG(LogLevel::Info
, "Initialize()");
884 RefPtr
<IVirtualDesktopManager
> desktopManager
;
885 HRESULT hr
= ::CoCreateInstance(
886 CLSID_VirtualDesktopManager
, NULL
, CLSCTX_INPROC_SERVER
,
887 __uuidof(IVirtualDesktopManager
), getter_AddRefs(desktopManager
));
891 mVirtualDesktopManager
= desktopManager
;
895 void WinWindowOcclusionTracker::WindowOcclusionCalculator::Shutdown() {
896 MonitorAutoLock
lock(mMonitor
);
898 MOZ_ASSERT(IsInWinWindowOcclusionThread());
899 CALC_LOG(LogLevel::Info
, "Shutdown()");
901 UnregisterEventHooks();
902 if (mOcclusionUpdateRunnable
) {
903 mOcclusionUpdateRunnable
->Cancel();
904 mOcclusionUpdateRunnable
= nullptr;
906 mVirtualDesktopManager
= nullptr;
908 mMonitor
.NotifyAll();
911 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
912 EnableOcclusionTrackingForWindow(HWND aHwnd
) {
913 MOZ_ASSERT(IsInWinWindowOcclusionThread());
914 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
915 CALC_LOG(LogLevel::Info
, "EnableOcclusionTrackingForWindow() aHwnd %p",
918 MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState
.find(aHwnd
) ==
919 mRootWindowHwndsOcclusionState
.end());
920 mRootWindowHwndsOcclusionState
[aHwnd
] = OcclusionState::UNKNOWN
;
922 if (mGlobalEventHooks
.empty()) {
923 RegisterEventHooks();
926 // Schedule an occlusion calculation so that the newly tracked window does
927 // not have a stale occlusion status.
928 ScheduleOcclusionCalculationIfNeeded();
931 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
932 DisableOcclusionTrackingForWindow(HWND aHwnd
) {
933 MOZ_ASSERT(IsInWinWindowOcclusionThread());
934 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
935 CALC_LOG(LogLevel::Info
, "DisableOcclusionTrackingForWindow() aHwnd %p",
938 MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState
.find(aHwnd
) !=
939 mRootWindowHwndsOcclusionState
.end());
940 mRootWindowHwndsOcclusionState
.erase(aHwnd
);
942 if (mMovingWindow
== aHwnd
) {
946 if (mRootWindowHwndsOcclusionState
.empty()) {
947 UnregisterEventHooks();
948 if (mOcclusionUpdateRunnable
) {
949 mOcclusionUpdateRunnable
->Cancel();
950 mOcclusionUpdateRunnable
= nullptr;
955 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
956 HandleVisibilityChanged(bool aVisible
) {
957 MOZ_ASSERT(IsInWinWindowOcclusionThread());
958 CALC_LOG(LogLevel::Info
, "HandleVisibilityChange() aVisible %d", aVisible
);
960 // May have gone from having no visible windows to having one, in
961 // which case we need to register event hooks, and make sure that an
962 // occlusion calculation is scheduled.
964 MaybeRegisterEventHooks();
965 ScheduleOcclusionCalculationIfNeeded();
969 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
970 HandleTriggerCalculation() {
971 MOZ_ASSERT(IsInWinWindowOcclusionThread());
972 CALC_LOG(LogLevel::Info
, "HandleTriggerCalculation()");
974 MaybeRegisterEventHooks();
975 ScheduleOcclusionCalculationIfNeeded();
978 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
979 MaybeRegisterEventHooks() {
980 if (mGlobalEventHooks
.empty()) {
981 RegisterEventHooks();
987 WinWindowOcclusionTracker::WindowOcclusionCalculator::EventHookCallback(
988 HWINEVENTHOOK aWinEventHook
, DWORD aEvent
, HWND aHwnd
, LONG aIdObject
,
989 LONG aIdChild
, DWORD aEventThread
, DWORD aMsEventTime
) {
991 sCalculator
->ProcessEventHookCallback(aWinEventHook
, aEvent
, aHwnd
,
992 aIdObject
, aIdChild
);
997 BOOL CALLBACK
WinWindowOcclusionTracker::WindowOcclusionCalculator::
998 ComputeNativeWindowOcclusionStatusCallback(HWND aHwnd
, LPARAM aLParam
) {
1000 return sCalculator
->ProcessComputeNativeWindowOcclusionStatusCallback(
1001 aHwnd
, reinterpret_cast<std::unordered_set
<DWORD
>*>(aLParam
));
1007 BOOL CALLBACK
WinWindowOcclusionTracker::WindowOcclusionCalculator::
1008 UpdateVisibleWindowProcessIdsCallback(HWND aHwnd
, LPARAM aLParam
) {
1010 sCalculator
->ProcessUpdateVisibleWindowProcessIdsCallback(aHwnd
);
1016 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1017 UpdateVisibleWindowProcessIds() {
1018 mPidsForLocationChangeHook
.clear();
1019 ::EnumWindows(&UpdateVisibleWindowProcessIdsCallback
, 0);
1022 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1023 ComputeNativeWindowOcclusionStatus() {
1024 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1025 MOZ_ASSERT(mSerializedTaskDispatcher
->IsOnCurrentThread());
1027 if (mOcclusionUpdateRunnable
) {
1028 mOcclusionUpdateRunnable
= nullptr;
1031 if (mRootWindowHwndsOcclusionState
.empty()) {
1035 // Set up initial conditions for occlusion calculation.
1036 bool shouldUnregisterEventHooks
= true;
1038 // Compute the LayoutDeviceIntRegion for the screen.
1039 int screenLeft
= ::GetSystemMetrics(SM_XVIRTUALSCREEN
);
1040 int screenTop
= ::GetSystemMetrics(SM_YVIRTUALSCREEN
);
1041 int screenWidth
= ::GetSystemMetrics(SM_CXVIRTUALSCREEN
);
1042 int screenHeight
= ::GetSystemMetrics(SM_CYVIRTUALSCREEN
);
1043 LayoutDeviceIntRegion screenRegion
=
1044 LayoutDeviceIntRect(screenLeft
, screenTop
, screenWidth
, screenHeight
);
1045 mNumRootWindowsWithUnknownOcclusionState
= 0;
1047 CALC_LOG(LogLevel::Debug
,
1048 "ComputeNativeWindowOcclusionStatus() screen(%d, %d, %d, %d)",
1049 screenLeft
, screenTop
, screenWidth
, screenHeight
);
1051 for (auto& [hwnd
, state
] : mRootWindowHwndsOcclusionState
) {
1052 // IsIconic() checks for a minimized window. Immediately set the state of
1053 // minimized windows to HIDDEN.
1054 if (::IsIconic(hwnd
)) {
1055 state
= OcclusionState::HIDDEN
;
1056 } else if (IsWindowOnCurrentVirtualDesktop(hwnd
) == Some(false)) {
1057 // If window is not on the current virtual desktop, immediately
1058 // set the state of the window to OCCLUDED.
1059 state
= OcclusionState::OCCLUDED
;
1060 // Don't unregister event hooks when not on current desktop. There's no
1061 // notification when that changes, so we can't reregister event hooks.
1062 shouldUnregisterEventHooks
= false;
1064 state
= OcclusionState::UNKNOWN
;
1065 shouldUnregisterEventHooks
= false;
1066 mNumRootWindowsWithUnknownOcclusionState
++;
1070 // Unregister event hooks if all native windows are minimized.
1071 if (shouldUnregisterEventHooks
) {
1072 UnregisterEventHooks();
1074 std::unordered_set
<DWORD
> currentPidsWithVisibleWindows
;
1075 mUnoccludedDesktopRegion
= screenRegion
;
1076 // Calculate unoccluded region if there is a non-minimized native window.
1077 // Also compute |current_pids_with_visible_windows| as we enumerate
1079 EnumWindows(&ComputeNativeWindowOcclusionStatusCallback
,
1080 reinterpret_cast<LPARAM
>(¤tPidsWithVisibleWindows
));
1081 // Check if mPidsForLocationChangeHook has any pids of processes
1082 // currently without visible windows. If so, unhook the win event,
1083 // remove the pid from mPidsForLocationChangeHook and remove
1084 // the corresponding event hook from mProcessEventHooks.
1085 std::unordered_set
<DWORD
> pidsToRemove
;
1086 for (auto locChangePid
: mPidsForLocationChangeHook
) {
1087 if (currentPidsWithVisibleWindows
.find(locChangePid
) ==
1088 currentPidsWithVisibleWindows
.end()) {
1089 // Remove the event hook from our map, and unregister the event hook.
1090 // It's possible the eventhook will no longer be valid, but if we don't
1091 // unregister the event hook, a process that toggles between having
1092 // visible windows and not having visible windows could cause duplicate
1093 // event hooks to get registered for the process.
1094 UnhookWinEvent(mProcessEventHooks
[locChangePid
]);
1095 mProcessEventHooks
.erase(locChangePid
);
1096 pidsToRemove
.insert(locChangePid
);
1099 if (!pidsToRemove
.empty()) {
1101 for (auto it
= mPidsForLocationChangeHook
.begin();
1102 it
!= mPidsForLocationChangeHook
.end();) {
1103 if (pidsToRemove
.find(*it
) != pidsToRemove
.end()) {
1104 it
= mPidsForLocationChangeHook
.erase(it
);
1112 std::unordered_map
<HWND
, OcclusionState
>* map
=
1113 &mRootWindowHwndsOcclusionState
;
1114 bool showAllWindows
= mShowingThumbnails
;
1115 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
1116 "CallUpdateOcclusionState", [map
, showAllWindows
]() {
1117 WinWindowOcclusionTracker::CallUpdateOcclusionState(map
,
1120 mSerializedTaskDispatcher
->PostTaskToMain(runnable
.forget());
1123 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1124 ScheduleOcclusionCalculationIfNeeded() {
1125 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1127 // OcclusionUpdateRunnable is already queued.
1128 if (mOcclusionUpdateRunnable
) {
1132 CALC_LOG(LogLevel::Debug
, "ScheduleOcclusionCalculationIfNeeded()");
1134 RefPtr
<CancelableRunnable
> task
= new OcclusionUpdateRunnable(this);
1135 mOcclusionUpdateRunnable
= task
;
1136 mSerializedTaskDispatcher
->PostDelayedTaskToCalculator(
1137 task
.forget(), kOcclusionUpdateRunnableDelayMs
);
1140 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1141 RegisterGlobalEventHook(DWORD aEventMin
, DWORD aEventMax
) {
1142 HWINEVENTHOOK eventHook
=
1143 ::SetWinEventHook(aEventMin
, aEventMax
, nullptr, &EventHookCallback
, 0, 0,
1144 WINEVENT_OUTOFCONTEXT
);
1145 mGlobalEventHooks
.push_back(eventHook
);
1148 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1149 RegisterEventHookForProcess(DWORD aPid
) {
1150 mPidsForLocationChangeHook
.insert(aPid
);
1151 mProcessEventHooks
[aPid
] = SetWinEventHook(
1152 EVENT_OBJECT_LOCATIONCHANGE
, EVENT_OBJECT_LOCATIONCHANGE
, nullptr,
1153 &EventHookCallback
, aPid
, 0, WINEVENT_OUTOFCONTEXT
);
1156 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1157 RegisterEventHooks() {
1158 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1159 MOZ_RELEASE_ASSERT(mGlobalEventHooks
.empty());
1160 CALC_LOG(LogLevel::Info
, "RegisterEventHooks()");
1162 // Detects native window lost mouse capture
1163 RegisterGlobalEventHook(EVENT_SYSTEM_CAPTUREEND
, EVENT_SYSTEM_CAPTUREEND
);
1165 // Detects native window move (drag) and resizing events.
1166 RegisterGlobalEventHook(EVENT_SYSTEM_MOVESIZESTART
, EVENT_SYSTEM_MOVESIZEEND
);
1168 // Detects native window minimize and restore from taskbar events.
1169 RegisterGlobalEventHook(EVENT_SYSTEM_MINIMIZESTART
, EVENT_SYSTEM_MINIMIZEEND
);
1171 // Detects foreground window changing.
1172 RegisterGlobalEventHook(EVENT_SYSTEM_FOREGROUND
, EVENT_SYSTEM_FOREGROUND
);
1174 // Detects objects getting shown and hidden. Used to know when the task bar
1175 // and alt tab are showing preview windows so we can unocclude windows.
1176 RegisterGlobalEventHook(EVENT_OBJECT_SHOW
, EVENT_OBJECT_HIDE
);
1178 // Detects object state changes, e.g., enable/disable state, native window
1179 // maximize and native window restore events.
1180 RegisterGlobalEventHook(EVENT_OBJECT_STATECHANGE
, EVENT_OBJECT_STATECHANGE
);
1182 // Cloaking and uncloaking of windows should trigger an occlusion calculation.
1183 // In particular, switching virtual desktops seems to generate these events.
1184 RegisterGlobalEventHook(EVENT_OBJECT_CLOAKED
, EVENT_OBJECT_UNCLOAKED
);
1186 // Determine which subset of processes to set EVENT_OBJECT_LOCATIONCHANGE on
1187 // because otherwise event throughput is very high, as it generates events
1188 // for location changes of all objects, including the mouse moving on top of a
1190 UpdateVisibleWindowProcessIds();
1191 for (DWORD pid
: mPidsForLocationChangeHook
) {
1192 RegisterEventHookForProcess(pid
);
1196 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1197 UnregisterEventHooks() {
1198 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1199 CALC_LOG(LogLevel::Info
, "UnregisterEventHooks()");
1201 for (const auto eventHook
: mGlobalEventHooks
) {
1202 ::UnhookWinEvent(eventHook
);
1204 mGlobalEventHooks
.clear();
1206 for (const auto& [pid
, eventHook
] : mProcessEventHooks
) {
1207 ::UnhookWinEvent(eventHook
);
1209 mProcessEventHooks
.clear();
1211 mPidsForLocationChangeHook
.clear();
1214 bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
1215 ProcessComputeNativeWindowOcclusionStatusCallback(
1216 HWND aHwnd
, std::unordered_set
<DWORD
>* aCurrentPidsWithVisibleWindows
) {
1217 LayoutDeviceIntRegion currUnoccludedDestkop
= mUnoccludedDesktopRegion
;
1218 LayoutDeviceIntRect windowRect
;
1219 bool windowIsOccluding
=
1220 WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd
, &windowRect
);
1221 if (windowIsOccluding
) {
1222 // Hook this window's process with EVENT_OBJECT_LOCATION_CHANGE, if we are
1223 // not already doing so.
1225 ::GetWindowThreadProcessId(aHwnd
, &pid
);
1226 aCurrentPidsWithVisibleWindows
->insert(pid
);
1227 auto it
= mProcessEventHooks
.find(pid
);
1228 if (it
== mProcessEventHooks
.end()) {
1229 RegisterEventHookForProcess(pid
);
1232 // If no more root windows to consider, return true so we can continue
1233 // looking for windows we haven't hooked.
1234 if (mNumRootWindowsWithUnknownOcclusionState
== 0) {
1238 mUnoccludedDesktopRegion
.SubOut(windowRect
);
1239 } else if (mNumRootWindowsWithUnknownOcclusionState
== 0) {
1240 // This window can't occlude other windows, but we've determined the
1241 // occlusion state of all root windows, so we can return.
1245 // Ignore moving windows when deciding if windows under it are occluded.
1246 if (aHwnd
== mMovingWindow
) {
1250 // Check if |hwnd| is a root window; if so, we're done figuring out
1251 // if it's occluded because we've seen all the windows "over" it.
1252 auto it
= mRootWindowHwndsOcclusionState
.find(aHwnd
);
1253 if (it
== mRootWindowHwndsOcclusionState
.end() ||
1254 it
->second
!= OcclusionState::UNKNOWN
) {
1258 CALC_LOG(LogLevel::Debug
,
1259 "ProcessComputeNativeWindowOcclusionStatusCallback() windowRect(%d, "
1260 "%d, %d, %d) IsOccluding %d",
1261 windowRect
.x
, windowRect
.y
, windowRect
.width
, windowRect
.height
,
1264 // On Win7, default theme makes root windows have complex regions by
1265 // default. But we can still check if their bounding rect is occluded.
1266 if (!windowIsOccluding
) {
1268 if (::GetWindowRect(aHwnd
, &rect
) != 0) {
1269 LayoutDeviceIntRect
windowRect(
1270 rect
.left
, rect
.top
, rect
.right
- rect
.left
, rect
.bottom
- rect
.top
);
1271 currUnoccludedDestkop
.SubOut(windowRect
);
1275 it
->second
= (mUnoccludedDesktopRegion
== currUnoccludedDestkop
)
1276 ? OcclusionState::OCCLUDED
1277 : OcclusionState::VISIBLE
;
1278 mNumRootWindowsWithUnknownOcclusionState
--;
1283 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1284 ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook
, DWORD aEvent
,
1285 HWND aHwnd
, LONG aIdObject
, LONG aIdChild
) {
1286 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1288 // No need to calculate occlusion if a zero HWND generated the event. This
1289 // happens if there is no window associated with the event, e.g., mouse move
1295 // We only care about events for window objects. In particular, we don't care
1296 // about OBJID_CARET, which is spammy.
1297 if (aIdObject
!= OBJID_WINDOW
) {
1301 CALC_LOG(LogLevel::Debug
,
1302 "WindowOcclusionCalculator::ProcessEventHookCallback() aEvent 0x%lx",
1305 // We generally ignore events for popup windows, except for when the taskbar
1306 // is hidden or Windows Taskbar, in which case we recalculate occlusion.
1307 // XXX Chrome Widget popup handling is removed for now.
1308 bool calculateOcclusion
= true;
1309 if (::GetWindowLong(aHwnd
, GWL_STYLE
) & WS_POPUP
) {
1310 nsAutoString className
;
1311 if (WinUtils::GetClassName(aHwnd
, className
)) {
1312 calculateOcclusion
= className
.Equals(L
"Shell_TrayWnd");
1316 // Detect if either the alt tab view or the task list thumbnail is being
1317 // shown. If so, mark all non-hidden windows as occluded, and remember that
1318 // we're in the showing_thumbnails state. This lasts until we get told that
1319 // either the alt tab view or task list thumbnail are hidden.
1320 if (aEvent
== EVENT_OBJECT_SHOW
) {
1321 // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
1323 if (mShowingThumbnails
) {
1326 nsAutoString className
;
1327 if (WinUtils::GetClassName(aHwnd
, className
)) {
1328 const auto name
= NS_ConvertUTF16toUTF8(className
);
1329 CALC_LOG(LogLevel::Debug
,
1330 "ProcessEventHookCallback() EVENT_OBJECT_SHOW %s", name
.get());
1332 if (name
.Equals("MultitaskingViewFrame") ||
1333 name
.Equals("TaskListThumbnailWnd")) {
1334 CALC_LOG(LogLevel::Info
,
1335 "ProcessEventHookCallback() mShowingThumbnails = true");
1336 mShowingThumbnails
= true;
1338 std::unordered_map
<HWND
, OcclusionState
>* map
=
1339 &mRootWindowHwndsOcclusionState
;
1340 bool showAllWindows
= mShowingThumbnails
;
1341 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
1342 "CallUpdateOcclusionState", [map
, showAllWindows
]() {
1343 WinWindowOcclusionTracker::CallUpdateOcclusionState(
1344 map
, showAllWindows
);
1346 mSerializedTaskDispatcher
->PostTaskToMain(runnable
.forget());
1350 } else if (aEvent
== EVENT_OBJECT_HIDE
) {
1351 // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
1353 if (!mShowingThumbnails
) {
1356 nsAutoString className
;
1357 WinUtils::GetClassName(aHwnd
, className
);
1358 const auto name
= NS_ConvertUTF16toUTF8(className
);
1359 CALC_LOG(LogLevel::Debug
, "ProcessEventHookCallback() EVENT_OBJECT_HIDE %s",
1361 if (name
.Equals("MultitaskingViewFrame") ||
1362 name
.Equals("TaskListThumbnailWnd")) {
1363 CALC_LOG(LogLevel::Info
,
1364 "ProcessEventHookCallback() mShowingThumbnails = false");
1365 mShowingThumbnails
= false;
1366 // Let occlusion calculation fix occlusion state, even though hwnd might
1367 // be a popup window.
1368 calculateOcclusion
= true;
1373 // Don't continually calculate occlusion while a window is moving (unless it's
1374 // a root window), but instead once at the beginning and once at the end.
1375 // Remember the window being moved so if it's a root window, we can ignore
1376 // it when deciding if windows under it are occluded.
1377 else if (aEvent
== EVENT_SYSTEM_MOVESIZESTART
) {
1378 mMovingWindow
= aHwnd
;
1379 } else if (aEvent
== EVENT_SYSTEM_MOVESIZEEND
) {
1381 } else if (mMovingWindow
!= 0) {
1382 if (aEvent
== EVENT_OBJECT_LOCATIONCHANGE
||
1383 aEvent
== EVENT_OBJECT_STATECHANGE
) {
1384 // Ignore move events if it's not a root window that's being moved. If it
1385 // is a root window, we want to calculate occlusion to support tab
1386 // dragging to windows that were occluded when the drag was started but
1387 // are no longer occluded.
1388 if (mRootWindowHwndsOcclusionState
.find(aHwnd
) ==
1389 mRootWindowHwndsOcclusionState
.end()) {
1393 // If we get an event that isn't a location/state change, then we probably
1394 // missed the movesizeend notification, or got events out of order. In
1395 // that case, we want to go back to normal occlusion calculation.
1400 if (!calculateOcclusion
) {
1404 ScheduleOcclusionCalculationIfNeeded();
1407 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1408 ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd
) {
1409 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1411 LayoutDeviceIntRect windowRect
;
1412 if (WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd
, &windowRect
)) {
1414 ::GetWindowThreadProcessId(aHwnd
, &pid
);
1415 mPidsForLocationChangeHook
.insert(pid
);
1419 bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
1420 WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
1421 HWND aHwnd
, LayoutDeviceIntRect
* aWindowRect
) {
1422 return IsWindowVisibleAndFullyOpaque(aHwnd
, aWindowRect
) &&
1423 (IsWindowOnCurrentVirtualDesktop(aHwnd
) == Some(true));
1426 Maybe
<bool> WinWindowOcclusionTracker::WindowOcclusionCalculator::
1427 IsWindowOnCurrentVirtualDesktop(HWND aHwnd
) {
1428 if (!mVirtualDesktopManager
) {
1432 BOOL onCurrentDesktop
;
1433 HRESULT hr
= mVirtualDesktopManager
->IsWindowOnCurrentVirtualDesktop(
1434 aHwnd
, &onCurrentDesktop
);
1436 // In this case, we do not know the window is in which virtual desktop.
1440 if (onCurrentDesktop
) {
1445 hr
= mVirtualDesktopManager
->GetWindowDesktopId(aHwnd
, &workspaceGuid
);
1447 // In this case, we do not know the window is in which virtual desktop.
1451 // IsWindowOnCurrentVirtualDesktop() is flaky for newly opened windows,
1452 // which causes test flakiness. Occasionally, it incorrectly says a window
1453 // is not on the current virtual desktop when it is. In this situation,
1454 // it also returns GUID_NULL for the desktop id.
1455 if (workspaceGuid
== GUID_NULL
) {
1456 // In this case, we do not know if the window is in which virtual desktop.
1457 // But we hanle it as on current virtual desktop.
1458 // It does not cause a problem to window occlusion.
1459 // Since if window is not on current virtual desktop, window size becomes
1460 // (0, 0, 0, 0). It makes window occlusion handling explicit. It is
1461 // necessary for gtest.
1471 } // namespace mozilla::widget