Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / WinWindowOcclusionTracker.cpp
bloba38f9505856e728df938d819b4e9e1b5dce31f34
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/. */
7 #include <queue>
8 #include <windows.h>
9 #include <winuser.h>
10 #include <wtsapi32.h>
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"
26 #include "nsWindow.h"
27 #include "transport/runnable_utils.h"
28 #include "WinUtils.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 {
45 public:
46 explicit OcclusionUpdateRunnable(
47 WinWindowOcclusionTracker::WindowOcclusionCalculator*
48 aOcclusionCalculator)
49 : CancelableRunnable("OcclusionUpdateRunnable"),
50 mOcclusionCalculator(aOcclusionCalculator) {
51 mTimeStamp = TimeStamp::Now();
54 NS_IMETHOD Run() override {
55 if (mIsCanceled) {
56 return NS_OK;
58 MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
60 uint32_t latencyMs =
61 round((TimeStamp::Now() - mTimeStamp).ToMilliseconds());
62 CALC_LOG(LogLevel::Debug,
63 "ComputeNativeWindowOcclusionStatus() latencyMs %u", latencyMs);
65 mOcclusionCalculator->ComputeNativeWindowOcclusionStatus();
66 return NS_OK;
69 nsresult Cancel() override {
70 mIsCanceled = true;
71 mOcclusionCalculator = nullptr;
72 return NS_OK;
75 private:
76 bool mIsCanceled = false;
77 RefPtr<WinWindowOcclusionTracker::WindowOcclusionCalculator>
78 mOcclusionCalculator;
79 TimeStamp mTimeStamp;
82 // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
83 class SerializedTaskDispatcher {
84 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SerializedTaskDispatcher)
86 public:
87 SerializedTaskDispatcher();
89 void Destroy();
90 void PostTaskToMain(already_AddRefed<nsIRunnable> aTask);
91 void PostTaskToCalculator(already_AddRefed<nsIRunnable> aTask);
92 void PostDelayedTaskToCalculator(already_AddRefed<Runnable> aTask,
93 int aDelayMs);
94 bool IsOnCurrentThread();
96 private:
97 friend class DelayedTaskRunnable;
99 ~SerializedTaskDispatcher();
101 struct Data {
102 std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
103 mTasks;
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);
111 void HandleTasks();
113 // Hold current EventTarget during calling nsIRunnable::Run().
114 RefPtr<nsISerialEventTarget> mCurrentEventTarget = nullptr;
116 DataMutex<Data> mData;
119 class DelayedTaskRunnable : public Runnable {
120 public:
121 DelayedTaskRunnable(SerializedTaskDispatcher* aSerializedTaskDispatcher,
122 already_AddRefed<Runnable> aTask)
123 : Runnable("DelayedTaskRunnable"),
124 mSerializedTaskDispatcher(aSerializedTaskDispatcher),
125 mTask(aTask) {}
127 NS_IMETHOD Run() override {
128 mSerializedTaskDispatcher->HandleDelayedTask(mTask.forget());
129 return NS_OK;
132 private:
133 RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
134 RefPtr<Runnable> mTask;
137 SerializedTaskDispatcher::SerializedTaskDispatcher()
138 : mData("SerializedTaskDispatcher::mData") {
139 MOZ_RELEASE_ASSERT(NS_IsMainThread());
140 LOG(LogLevel::Info,
141 "SerializedTaskDispatcher::SerializedTaskDispatcher() this %p", this);
144 SerializedTaskDispatcher::~SerializedTaskDispatcher() {
145 #ifdef DEBUG
146 auto data = mData.Lock();
147 MOZ_ASSERT(data->mDestroyed);
148 MOZ_ASSERT(data->mTasks.empty());
149 #endif
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) {
158 return;
161 data->mDestroyed = true;
162 std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
163 empty;
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) {
173 return;
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) {
189 return;
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) {
222 return;
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) {
241 return;
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;
255 // Get front task
257 auto data = mData.Lock();
258 if (data->mDestroyed) {
259 return;
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;
270 while (frontTask) {
271 if (NS_IsMainThread()) {
272 LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
273 } else {
274 CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
277 MOZ_ASSERT_IF(NS_IsMainThread(),
278 mCurrentEventTarget == GetMainThreadSerialEventTarget());
279 MOZ_ASSERT_IF(
280 !NS_IsMainThread(),
281 mCurrentEventTarget == MessageLoop::current()->SerialEventTarget());
283 frontTask->Run();
285 // Get next task
287 auto data = mData.Lock();
288 if (data->mDestroyed) {
289 return;
292 frontTask = nullptr;
293 data->mTasks.pop();
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()) {
311 return;
314 PostTasksIfNecessary(data->mTasks.front().second, data);
318 // static
319 StaticRefPtr<WinWindowOcclusionTracker> WinWindowOcclusionTracker::sTracker;
321 /* static */
322 WinWindowOcclusionTracker* WinWindowOcclusionTracker::Get() {
323 MOZ_ASSERT(NS_IsMainThread());
324 if (!sTracker || sTracker->mHasAttemptedShutdown) {
325 return nullptr;
327 return sTracker;
330 /* static */
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;
338 if (sTracker) {
339 // Try to reuse the thread, which involves stopping and restarting it.
340 sTracker->mThread->Stop();
341 if (sTracker->mThread->StartWithOptions(options)) {
342 // Success!
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();
350 return;
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.
355 sTracker = nullptr;
358 UniquePtr<base::Thread> thread =
359 MakeUnique<base::Thread>("WinWindowOcclusionCalc");
361 if (!thread->StartWithOptions(options)) {
362 return;
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());
375 /* static */
376 void WinWindowOcclusionTracker::ShutDown() {
377 if (!sTracker) {
378 return;
381 MOZ_ASSERT(NS_IsMainThread());
382 LOG(LogLevel::Info, "WinWindowOcclusionTracker::ShutDown()");
384 sTracker->mHasAttemptedShutdown = true;
385 sTracker->Destroy();
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.
395 CVStatus status;
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();
418 do {
419 status = sTracker->mMonitor.Wait(TIMEOUT);
420 } while ((status == CVStatus::Timeout) &&
421 ((TimeStamp::NowLoRes() - timeStart) < TIMEOUT));
424 if (status == CVStatus::NoTimeout) {
425 WindowOcclusionCalculator::ClearInstance();
426 sTracker = nullptr;
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();
444 /* static */
445 MessageLoop* WinWindowOcclusionTracker::OcclusionCalculatorLoop() {
446 return sTracker ? sTracker->mThread->message_loop() : nullptr;
449 /* static */
450 bool WinWindowOcclusionTracker::IsInWinWindowOcclusionThread() {
451 return sTracker &&
452 sTracker->mThread->thread_id() == PlatformThread::CurrentId();
455 void WinWindowOcclusionTracker::EnsureDisplayStatusObserver() {
456 if (mDisplayStatusObserver) {
457 return;
459 if (StaticPrefs::
460 widget_windows_window_occlusion_tracking_display_state_enabled()) {
461 mDisplayStatusObserver = DisplayStatusObserver::Create(this);
465 void WinWindowOcclusionTracker::EnsureSessionChangeObserver() {
466 if (mSessionChangeObserver) {
467 return;
469 if (StaticPrefs::
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",
478 aWindow, aHwnd);
480 auto it = mHwndRootWindowMap.find(aHwnd);
481 if (it != mHwndRootWindowMap.end()) {
482 return;
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());
497 LOG(LogLevel::Info,
498 "WinWindowOcclusionTracker::Disable() aWindow %p aHwnd %p", aWindow,
499 aHwnd);
501 auto it = mHwndRootWindowMap.find(aHwnd);
502 if (it == mHwndRootWindowMap.end()) {
503 return;
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,
516 bool aVisible) {
517 MOZ_ASSERT(NS_IsMainThread());
518 LOG(LogLevel::Info,
519 "WinWindowOcclusionTracker::OnWindowVisibilityChanged() aWindow %p "
520 "aVisible %d",
521 aWindow, aVisible);
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());
544 LOG(LogLevel::Info,
545 "WinWindowOcclusionTracker::~WinWindowOcclusionTracker()");
548 // static
549 bool WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
550 HWND aHwnd, LayoutDeviceIntRect* aWindowRect) {
551 // Filter out windows that are not "visible", IsWindowVisible().
552 if (!::IsWindow(aHwnd) || !::IsWindowVisible(aHwnd)) {
553 return false;
556 // Filter out minimized windows.
557 if (::IsIconic(aHwnd)) {
558 return false;
561 LONG exStyles = ::GetWindowLong(aHwnd, GWL_EXSTYLE);
562 // Filter out "transparent" windows, windows where the mouse clicks fall
563 // through them.
564 if (exStyles & WS_EX_TRANSPARENT) {
565 return false;
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
572 // Windows Taskbar.
573 if (exStyles & WS_EX_TOOLWINDOW) {
574 nsAutoString className;
575 if (WinUtils::GetClassName(aHwnd, className)) {
576 if (!className.Equals(L"Shell_TrayWnd")) {
577 return false;
582 // Filter out layered windows that are not opaque or that set a transparency
583 // colorkey.
584 if (exStyles & WS_EX_LAYERED) {
585 BYTE alpha;
586 DWORD flags;
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)) {
595 return false;
598 if (flags & LWA_ALPHA && alpha < 255) {
599 return false;
601 if (flags & LWA_COLORKEY) {
602 return false;
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) {
611 return false;
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.
618 DWORD reason;
619 if (SUCCEEDED(::DwmGetWindowAttribute(aHwnd, DWMWA_CLOAKED, &reason,
620 sizeof(reason))) &&
621 reason != 0) {
622 return false;
625 RECT winRect;
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)) {
629 return false;
631 if (::IsRectEmpty(&winRect)) {
632 return false;
635 // Ignore popup windows since they're transient unless it is the Windows
636 // Taskbar
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")) {
642 return false;
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);
661 if (hmon) {
662 MONITORINFO mi;
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;
672 } else {
673 aWindowRect->x = std::min(workArea.x + workArea.width,
674 aWindowRect->x + aWindowRect->width) -
675 aWindowRect->width;
677 aWindowRect->height = std::min(workArea.height, aWindowRect->height);
678 if (aWindowRect->y < workArea.y) {
679 aWindowRect->y = workArea.y;
680 } else {
681 aWindowRect->y = std::min(workArea.y + workArea.height,
682 aWindowRect->y + aWindowRect->height) -
683 aWindowRect->height;
689 return true;
692 // static
693 void WinWindowOcclusionTracker::CallUpdateOcclusionState(
694 std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows) {
695 MOZ_ASSERT(NS_IsMainThread());
697 auto* tracker = WinWindowOcclusionTracker::Get();
698 if (!tracker) {
699 return;
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());
708 LOG(LogLevel::Debug,
709 "WinWindowOcclusionTracker::UpdateOcclusionState() aShowAllWindows %d",
710 aShowAllWindows);
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()) {
717 continue;
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);
729 if (!widget) {
730 continue;
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) {
745 return;
748 if (aStatusCode == WTS_SESSION_UNLOCK) {
749 LOG(LogLevel::Info,
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) {
756 LOG(LogLevel::Info,
757 "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_LOCK");
759 mScreenLocked = true;
760 MarkNonIconicWindowsOccluded();
764 void WinWindowOcclusionTracker::OnDisplayStateChanged(bool aDisplayOn) {
765 MOZ_ASSERT(NS_IsMainThread());
766 LOG(LogLevel::Info,
767 "WinWindowOcclusionTracker::OnDisplayStateChanged() aDisplayOn %d",
768 aDisplayOn);
770 if (mDisplayOn == aDisplayOn) {
771 return;
774 mDisplayOn = aDisplayOn;
775 if (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());
785 } else {
786 MarkNonIconicWindowsOccluded();
790 void WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded() {
791 MOZ_ASSERT(NS_IsMainThread());
792 LOG(LogLevel::Info,
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);
799 if (!widget) {
800 continue;
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());
818 // static
819 BOOL WinWindowOcclusionTracker::DumpOccludingWindowsCallback(HWND aHWnd,
820 LPARAM aLParam) {
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);
829 printf_stderr(
830 "DumpOccludingWindowsCallback() aHWnd %p className %s windowRect(%d, "
831 "%d, %d, %d)\n",
832 aHWnd, name.get(), windowRect.x, windowRect.y, windowRect.width,
833 windowRect.height);
837 if (aHWnd == hwnd) {
838 return false;
840 return true;
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));
849 // static
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() {}
866 // static
867 void WinWindowOcclusionTracker::WindowOcclusionCalculator::CreateInstance() {
868 MOZ_ASSERT(NS_IsMainThread());
869 sCalculator = new WindowOcclusionCalculator();
872 // static
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()");
883 #ifndef __MINGW32__
884 RefPtr<IVirtualDesktopManager> desktopManager;
885 HRESULT hr = ::CoCreateInstance(
886 CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
887 __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
888 if (FAILED(hr)) {
889 return;
891 mVirtualDesktopManager = desktopManager;
892 #endif
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",
916 aHwnd);
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",
936 aHwnd);
938 MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState.find(aHwnd) !=
939 mRootWindowHwndsOcclusionState.end());
940 mRootWindowHwndsOcclusionState.erase(aHwnd);
942 if (mMovingWindow == aHwnd) {
943 mMovingWindow = 0;
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.
963 if (aVisible) {
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();
985 // static
986 void CALLBACK
987 WinWindowOcclusionTracker::WindowOcclusionCalculator::EventHookCallback(
988 HWINEVENTHOOK aWinEventHook, DWORD aEvent, HWND aHwnd, LONG aIdObject,
989 LONG aIdChild, DWORD aEventThread, DWORD aMsEventTime) {
990 if (sCalculator) {
991 sCalculator->ProcessEventHookCallback(aWinEventHook, aEvent, aHwnd,
992 aIdObject, aIdChild);
996 // static
997 BOOL CALLBACK WinWindowOcclusionTracker::WindowOcclusionCalculator::
998 ComputeNativeWindowOcclusionStatusCallback(HWND aHwnd, LPARAM aLParam) {
999 if (sCalculator) {
1000 return sCalculator->ProcessComputeNativeWindowOcclusionStatusCallback(
1001 aHwnd, reinterpret_cast<std::unordered_set<DWORD>*>(aLParam));
1003 return FALSE;
1006 // static
1007 BOOL CALLBACK WinWindowOcclusionTracker::WindowOcclusionCalculator::
1008 UpdateVisibleWindowProcessIdsCallback(HWND aHwnd, LPARAM aLParam) {
1009 if (sCalculator) {
1010 sCalculator->ProcessUpdateVisibleWindowProcessIdsCallback(aHwnd);
1011 return TRUE;
1013 return FALSE;
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()) {
1032 return;
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;
1063 } else {
1064 state = OcclusionState::UNKNOWN;
1065 shouldUnregisterEventHooks = false;
1066 mNumRootWindowsWithUnknownOcclusionState++;
1070 // Unregister event hooks if all native windows are minimized.
1071 if (shouldUnregisterEventHooks) {
1072 UnregisterEventHooks();
1073 } else {
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
1078 // the windows.
1079 EnumWindows(&ComputeNativeWindowOcclusionStatusCallback,
1080 reinterpret_cast<LPARAM>(&currentPidsWithVisibleWindows));
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()) {
1100 // XXX simplify
1101 for (auto it = mPidsForLocationChangeHook.begin();
1102 it != mPidsForLocationChangeHook.end();) {
1103 if (pidsToRemove.find(*it) != pidsToRemove.end()) {
1104 it = mPidsForLocationChangeHook.erase(it);
1105 } else {
1106 ++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,
1118 showAllWindows);
1120 mSerializedTaskDispatcher->PostTaskToMain(runnable.forget());
1123 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1124 ScheduleOcclusionCalculationIfNeeded() {
1125 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1127 // OcclusionUpdateRunnable is already queued.
1128 if (mOcclusionUpdateRunnable) {
1129 return;
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
1189 // window.
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.
1224 DWORD pid;
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) {
1235 return true;
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.
1242 return true;
1245 // Ignore moving windows when deciding if windows under it are occluded.
1246 if (aHwnd == mMovingWindow) {
1247 return true;
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) {
1255 return true;
1258 CALC_LOG(LogLevel::Debug,
1259 "ProcessComputeNativeWindowOcclusionStatusCallback() windowRect(%d, "
1260 "%d, %d, %d) IsOccluding %d",
1261 windowRect.x, windowRect.y, windowRect.width, windowRect.height,
1262 windowIsOccluding);
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) {
1267 RECT rect;
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--;
1280 return true;
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
1290 // events.
1291 if (!aHwnd) {
1292 return;
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) {
1298 return;
1301 CALC_LOG(LogLevel::Debug,
1302 "WindowOcclusionCalculator::ProcessEventHookCallback() aEvent 0x%lx",
1303 aEvent);
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
1322 // needed.
1323 if (mShowingThumbnails) {
1324 return;
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());
1349 return;
1350 } else if (aEvent == EVENT_OBJECT_HIDE) {
1351 // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
1352 // needed.
1353 if (!mShowingThumbnails) {
1354 return;
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",
1360 name.get());
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;
1369 } else {
1370 return;
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) {
1380 mMovingWindow = 0;
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()) {
1390 return;
1392 } else {
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.
1396 mMovingWindow = 0;
1400 if (!calculateOcclusion) {
1401 return;
1404 ScheduleOcclusionCalculationIfNeeded();
1407 void WinWindowOcclusionTracker::WindowOcclusionCalculator::
1408 ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd) {
1409 MOZ_ASSERT(IsInWinWindowOcclusionThread());
1411 LayoutDeviceIntRect windowRect;
1412 if (WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd, &windowRect)) {
1413 DWORD pid;
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) {
1429 return Some(true);
1432 BOOL onCurrentDesktop;
1433 HRESULT hr = mVirtualDesktopManager->IsWindowOnCurrentVirtualDesktop(
1434 aHwnd, &onCurrentDesktop);
1435 if (FAILED(hr)) {
1436 // In this case, we do not know the window is in which virtual desktop.
1437 return Nothing();
1440 if (onCurrentDesktop) {
1441 return Some(true);
1444 GUID workspaceGuid;
1445 hr = mVirtualDesktopManager->GetWindowDesktopId(aHwnd, &workspaceGuid);
1446 if (FAILED(hr)) {
1447 // In this case, we do not know the window is in which virtual desktop.
1448 return Nothing();
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.
1462 return Some(true);
1465 return Some(false);
1468 #undef LOG
1469 #undef CALC_LOG
1471 } // namespace mozilla::widget