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