summaryrefslogtreecommitdiffstats
path: root/widget/windows/WinWindowOcclusionTracker.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /widget/windows/WinWindowOcclusionTracker.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--widget/windows/WinWindowOcclusionTracker.cpp1481
1 files changed, 1481 insertions, 0 deletions
diff --git a/widget/windows/WinWindowOcclusionTracker.cpp b/widget/windows/WinWindowOcclusionTracker.cpp
new file mode 100644
index 0000000000..4e382e70d5
--- /dev/null
+++ b/widget/windows/WinWindowOcclusionTracker.cpp
@@ -0,0 +1,1481 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <queue>
+#include <windows.h>
+#include <winuser.h>
+#include <wtsapi32.h>
+
+#include "WinWindowOcclusionTracker.h"
+
+#include "base/thread.h"
+#include "base/message_loop.h"
+#include "base/platform_thread.h"
+#include "gfxConfig.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPtr.h"
+#include "nsBaseWidget.h"
+#include "nsWindow.h"
+#include "transport/runnable_utils.h"
+#include "WinUtils.h"
+
+// Win 8+ (_WIN32_WINNT_WIN8)
+#ifndef EVENT_OBJECT_CLOAKED
+# define EVENT_OBJECT_CLOAKED 0x8017
+# define EVENT_OBJECT_UNCLOAKED 0x8018
+#endif
+
+namespace mozilla::widget {
+
+// Can be called on Main thread
+LazyLogModule gWinOcclusionTrackerLog("WinOcclusionTracker");
+#define LOG(type, ...) MOZ_LOG(gWinOcclusionTrackerLog, type, (__VA_ARGS__))
+
+// Can be called on OcclusionCalculator thread
+LazyLogModule gWinOcclusionCalculatorLog("WinOcclusionCalculator");
+#define CALC_LOG(type, ...) \
+ MOZ_LOG(gWinOcclusionCalculatorLog, type, (__VA_ARGS__))
+
+// ~16 ms = time between frames when frame rate is 60 FPS.
+const int kOcclusionUpdateRunnableDelayMs = 16;
+
+class OcclusionUpdateRunnable : public CancelableRunnable {
+ public:
+ explicit OcclusionUpdateRunnable(
+ WinWindowOcclusionTracker::WindowOcclusionCalculator*
+ aOcclusionCalculator)
+ : CancelableRunnable("OcclusionUpdateRunnable"),
+ mOcclusionCalculator(aOcclusionCalculator) {
+ mTimeStamp = TimeStamp::Now();
+ }
+
+ NS_IMETHOD Run() override {
+ if (mIsCanceled) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
+
+ uint32_t latencyMs =
+ round((TimeStamp::Now() - mTimeStamp).ToMilliseconds());
+ CALC_LOG(LogLevel::Debug,
+ "ComputeNativeWindowOcclusionStatus() latencyMs %u", latencyMs);
+
+ mOcclusionCalculator->ComputeNativeWindowOcclusionStatus();
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ mIsCanceled = true;
+ mOcclusionCalculator = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ bool mIsCanceled = false;
+ RefPtr<WinWindowOcclusionTracker::WindowOcclusionCalculator>
+ mOcclusionCalculator;
+ TimeStamp mTimeStamp;
+};
+
+// Used to serialize tasks related to mRootWindowHwndsOcclusionState.
+class SerializedTaskDispatcher {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SerializedTaskDispatcher)
+
+ public:
+ SerializedTaskDispatcher();
+
+ void Destroy();
+ void PostTaskToMain(already_AddRefed<nsIRunnable> aTask);
+ void PostTaskToCalculator(already_AddRefed<nsIRunnable> aTask);
+ void PostDelayedTaskToCalculator(already_AddRefed<Runnable> aTask,
+ int aDelayMs);
+ bool IsOnCurrentThread();
+
+ private:
+ friend class DelayedTaskRunnable;
+
+ ~SerializedTaskDispatcher();
+
+ struct Data {
+ std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
+ mTasks;
+ bool mDestroyed = false;
+ RefPtr<Runnable> mCurrentRunnable;
+ };
+
+ void PostTasksIfNecessary(nsISerialEventTarget* aEventTarget,
+ const DataMutex<Data>::AutoLock& aProofOfLock);
+ void HandleDelayedTask(already_AddRefed<nsIRunnable> aTask);
+ void HandleTasks();
+
+ // Hold current EventTarget during calling nsIRunnable::Run().
+ RefPtr<nsISerialEventTarget> mCurrentEventTarget = nullptr;
+
+ DataMutex<Data> mData;
+};
+
+class DelayedTaskRunnable : public Runnable {
+ public:
+ DelayedTaskRunnable(SerializedTaskDispatcher* aSerializedTaskDispatcher,
+ already_AddRefed<Runnable> aTask)
+ : Runnable("DelayedTaskRunnable"),
+ mSerializedTaskDispatcher(aSerializedTaskDispatcher),
+ mTask(aTask) {}
+
+ NS_IMETHOD Run() override {
+ mSerializedTaskDispatcher->HandleDelayedTask(mTask.forget());
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
+ RefPtr<Runnable> mTask;
+};
+
+SerializedTaskDispatcher::SerializedTaskDispatcher()
+ : mData("SerializedTaskDispatcher::mData") {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "SerializedTaskDispatcher::SerializedTaskDispatcher() this %p", this);
+}
+
+SerializedTaskDispatcher::~SerializedTaskDispatcher() {
+#ifdef DEBUG
+ auto data = mData.Lock();
+ MOZ_ASSERT(data->mDestroyed);
+ MOZ_ASSERT(data->mTasks.empty());
+#endif
+}
+
+void SerializedTaskDispatcher::Destroy() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "SerializedTaskDispatcher::Destroy() this %p", this);
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ data->mDestroyed = true;
+ std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
+ empty;
+ std::swap(data->mTasks, empty);
+}
+
+void SerializedTaskDispatcher::PostTaskToMain(
+ already_AddRefed<nsIRunnable> aTask) {
+ RefPtr<nsIRunnable> task = aTask;
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ nsISerialEventTarget* eventTarget = GetMainThreadSerialEventTarget();
+ data->mTasks.push({std::move(task), eventTarget});
+
+ MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
+ PostTasksIfNecessary(eventTarget, data);
+}
+
+void SerializedTaskDispatcher::PostTaskToCalculator(
+ already_AddRefed<nsIRunnable> aTask) {
+ RefPtr<nsIRunnable> task = aTask;
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ nsISerialEventTarget* eventTarget =
+ WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
+ data->mTasks.push({std::move(task), eventTarget});
+
+ MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
+ PostTasksIfNecessary(eventTarget, data);
+}
+
+void SerializedTaskDispatcher::PostDelayedTaskToCalculator(
+ already_AddRefed<Runnable> aTask, int aDelayMs) {
+ CALC_LOG(LogLevel::Debug,
+ "SerializedTaskDispatcher::PostDelayedTaskToCalculator()");
+
+ RefPtr<DelayedTaskRunnable> runnable =
+ new DelayedTaskRunnable(this, std::move(aTask));
+ MessageLoop* targetLoop =
+ WinWindowOcclusionTracker::OcclusionCalculatorLoop();
+ targetLoop->PostDelayedTask(runnable.forget(), aDelayMs);
+}
+
+bool SerializedTaskDispatcher::IsOnCurrentThread() {
+ return !!mCurrentEventTarget;
+}
+
+void SerializedTaskDispatcher::PostTasksIfNecessary(
+ nsISerialEventTarget* aEventTarget,
+ const DataMutex<Data>::AutoLock& aProofOfLock) {
+ MOZ_ASSERT(!aProofOfLock->mTasks.empty());
+
+ if (aProofOfLock->mCurrentRunnable) {
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<SerializedTaskDispatcher>(this),
+ &SerializedTaskDispatcher::HandleTasks);
+ aProofOfLock->mCurrentRunnable = runnable;
+ aEventTarget->Dispatch(runnable.forget());
+}
+
+void SerializedTaskDispatcher::HandleDelayedTask(
+ already_AddRefed<nsIRunnable> aTask) {
+ MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleDelayedTask()");
+
+ RefPtr<nsIRunnable> task = aTask;
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ nsISerialEventTarget* eventTarget =
+ WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
+ data->mTasks.push({std::move(task), eventTarget});
+
+ MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
+ PostTasksIfNecessary(eventTarget, data);
+}
+
+void SerializedTaskDispatcher::HandleTasks() {
+ RefPtr<nsIRunnable> frontTask;
+
+ // Get front task
+ {
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(data->mCurrentRunnable);
+ MOZ_RELEASE_ASSERT(!data->mTasks.empty());
+
+ frontTask = data->mTasks.front().first;
+
+ MOZ_RELEASE_ASSERT(!mCurrentEventTarget);
+ mCurrentEventTarget = data->mTasks.front().second;
+ }
+
+ while (frontTask) {
+ if (NS_IsMainThread()) {
+ LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
+ } else {
+ CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
+ }
+
+ MOZ_ASSERT_IF(NS_IsMainThread(),
+ mCurrentEventTarget == GetMainThreadSerialEventTarget());
+ MOZ_ASSERT_IF(
+ !NS_IsMainThread(),
+ mCurrentEventTarget == MessageLoop::current()->SerialEventTarget());
+
+ frontTask->Run();
+
+ // Get next task
+ {
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ frontTask = nullptr;
+ data->mTasks.pop();
+ // Check if next task could be handled on current thread
+ if (!data->mTasks.empty() &&
+ data->mTasks.front().second == mCurrentEventTarget) {
+ frontTask = data->mTasks.front().first;
+ }
+ }
+ }
+
+ MOZ_ASSERT(!frontTask);
+
+ // Post tasks to different thread if pending tasks exist.
+ {
+ auto data = mData.Lock();
+ data->mCurrentRunnable = nullptr;
+ mCurrentEventTarget = nullptr;
+
+ if (data->mDestroyed || data->mTasks.empty()) {
+ return;
+ }
+
+ PostTasksIfNecessary(data->mTasks.front().second, data);
+ }
+}
+
+// static
+StaticRefPtr<WinWindowOcclusionTracker> WinWindowOcclusionTracker::sTracker;
+
+/* static */
+WinWindowOcclusionTracker* WinWindowOcclusionTracker::Get() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sTracker || sTracker->mHasAttemptedShutdown) {
+ return nullptr;
+ }
+ return sTracker;
+}
+
+/* static */
+void WinWindowOcclusionTracker::Ensure() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::Ensure()");
+
+ base::Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_UI;
+
+ if (sTracker) {
+ // Try to reuse the thread, which involves stopping and restarting it.
+ sTracker->mThread->Stop();
+ if (sTracker->mThread->StartWithOptions(options)) {
+ // Success!
+ sTracker->mHasAttemptedShutdown = false;
+
+ // Take this opportunity to ensure that mDisplayStatusObserver and
+ // mSessionChangeObserver exist. They might have failed to be
+ // created when sTracker was created.
+ sTracker->EnsureDisplayStatusObserver();
+ sTracker->EnsureSessionChangeObserver();
+ return;
+ }
+ // Restart failed, so null out our sTracker and try again with a new
+ // thread. This will cause the old singleton instance to be deallocated,
+ // which will destroy its mThread as well.
+ sTracker = nullptr;
+ }
+
+ UniquePtr<base::Thread> thread =
+ MakeUnique<base::Thread>("WinWindowOcclusionCalc");
+
+ if (!thread->StartWithOptions(options)) {
+ return;
+ }
+
+ sTracker = new WinWindowOcclusionTracker(std::move(thread));
+ WindowOcclusionCalculator::CreateInstance();
+
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::Initialize);
+ sTracker->mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+/* static */
+void WinWindowOcclusionTracker::ShutDown() {
+ if (!sTracker) {
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::ShutDown()");
+
+ sTracker->mHasAttemptedShutdown = true;
+ sTracker->Destroy();
+
+ // Our thread could hang while we're waiting for it to stop.
+ // Since we're shutting down, that's not a critical problem.
+ // We set a reasonable amount of time to wait for shutdown,
+ // and if it succeeds within that time, we correctly stop
+ // our thread by nulling out the refptr, which will cause it
+ // to be deallocated and join the thread. If it times out,
+ // we do nothing, which means that the thread will not be
+ // joined and sTracker memory will leak.
+ CVStatus status;
+ {
+ // It's important to hold the lock before posting the
+ // runnable. This ensures that the runnable can't begin
+ // until we've started our Wait, which prevents us from
+ // Waiting on a monitor that has already been notified.
+ MonitorAutoLock lock(sTracker->mMonitor);
+
+ static const TimeDuration TIMEOUT = TimeDuration::FromSeconds(2.0);
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::Shutdown);
+ OcclusionCalculatorLoop()->PostTask(runnable.forget());
+
+ // Monitor uses SleepConditionVariableSRW, which can have
+ // spurious wakeups which are reported as timeouts, so we
+ // check timestamps to ensure that we've waited as long we
+ // intended to. If we wake early, we don't bother calculating
+ // a precise amount for the next wait; we just wait the same
+ // amount of time. This means timeout might happen after as
+ // much as 2x the TIMEOUT time.
+ TimeStamp timeStart = TimeStamp::NowLoRes();
+ do {
+ status = sTracker->mMonitor.Wait(TIMEOUT);
+ } while ((status == CVStatus::Timeout) &&
+ ((TimeStamp::NowLoRes() - timeStart) < TIMEOUT));
+ }
+
+ if (status == CVStatus::NoTimeout) {
+ WindowOcclusionCalculator::ClearInstance();
+ sTracker = nullptr;
+ }
+}
+
+void WinWindowOcclusionTracker::Destroy() {
+ if (mDisplayStatusObserver) {
+ mDisplayStatusObserver->Destroy();
+ mDisplayStatusObserver = nullptr;
+ }
+ if (mSessionChangeObserver) {
+ mSessionChangeObserver->Destroy();
+ mSessionChangeObserver = nullptr;
+ }
+ if (mSerializedTaskDispatcher) {
+ mSerializedTaskDispatcher->Destroy();
+ }
+}
+
+/* static */
+MessageLoop* WinWindowOcclusionTracker::OcclusionCalculatorLoop() {
+ return sTracker ? sTracker->mThread->message_loop() : nullptr;
+}
+
+/* static */
+bool WinWindowOcclusionTracker::IsInWinWindowOcclusionThread() {
+ return sTracker &&
+ sTracker->mThread->thread_id() == PlatformThread::CurrentId();
+}
+
+void WinWindowOcclusionTracker::EnsureDisplayStatusObserver() {
+ if (mDisplayStatusObserver) {
+ return;
+ }
+ if (StaticPrefs::
+ widget_windows_window_occlusion_tracking_display_state_enabled()) {
+ mDisplayStatusObserver = DisplayStatusObserver::Create(this);
+ }
+}
+
+void WinWindowOcclusionTracker::EnsureSessionChangeObserver() {
+ if (mSessionChangeObserver) {
+ return;
+ }
+ if (StaticPrefs::
+ widget_windows_window_occlusion_tracking_session_lock_enabled()) {
+ mSessionChangeObserver = SessionChangeObserver::Create(this);
+ }
+}
+
+void WinWindowOcclusionTracker::Enable(nsBaseWidget* aWindow, HWND aHwnd) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::Enable() aWindow %p aHwnd %p",
+ aWindow, aHwnd);
+
+ auto it = mHwndRootWindowMap.find(aHwnd);
+ if (it != mHwndRootWindowMap.end()) {
+ return;
+ }
+
+ nsWeakPtr weak = do_GetWeakReference(aWindow);
+ mHwndRootWindowMap.emplace(aHwnd, weak);
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::EnableOcclusionTrackingForWindow, aHwnd);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+void WinWindowOcclusionTracker::Disable(nsBaseWidget* aWindow, HWND aHwnd) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::Disable() aWindow %p aHwnd %p", aWindow,
+ aHwnd);
+
+ auto it = mHwndRootWindowMap.find(aHwnd);
+ if (it == mHwndRootWindowMap.end()) {
+ return;
+ }
+
+ mHwndRootWindowMap.erase(it);
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::DisableOcclusionTrackingForWindow, aHwnd);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+void WinWindowOcclusionTracker::OnWindowVisibilityChanged(nsBaseWidget* aWindow,
+ bool aVisible) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnWindowVisibilityChanged() aWindow %p "
+ "aVisible %d",
+ aWindow, aVisible);
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::HandleVisibilityChanged, aVisible);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+WinWindowOcclusionTracker::WinWindowOcclusionTracker(
+ UniquePtr<base::Thread> aThread)
+ : mThread(std::move(aThread)), mMonitor("WinWindowOcclusionTracker") {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::WinWindowOcclusionTracker()");
+
+ EnsureDisplayStatusObserver();
+ EnsureSessionChangeObserver();
+
+ mSerializedTaskDispatcher = new SerializedTaskDispatcher();
+}
+
+WinWindowOcclusionTracker::~WinWindowOcclusionTracker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::~WinWindowOcclusionTracker()");
+}
+
+// static
+bool WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
+ HWND aHwnd, LayoutDeviceIntRect* aWindowRect) {
+ // Filter out windows that are not "visible", IsWindowVisible().
+ if (!::IsWindow(aHwnd) || !::IsWindowVisible(aHwnd)) {
+ return false;
+ }
+
+ // Filter out minimized windows.
+ if (::IsIconic(aHwnd)) {
+ return false;
+ }
+
+ LONG exStyles = ::GetWindowLong(aHwnd, GWL_EXSTYLE);
+ // Filter out "transparent" windows, windows where the mouse clicks fall
+ // through them.
+ if (exStyles & WS_EX_TRANSPARENT) {
+ return false;
+ }
+
+ // Filter out "tool windows", which are floating windows that do not appear on
+ // the taskbar or ALT-TAB. Floating windows can have larger window rectangles
+ // than what is visible to the user, so by filtering them out we will avoid
+ // incorrectly marking native windows as occluded. We do not filter out the
+ // Windows Taskbar.
+ if (exStyles & WS_EX_TOOLWINDOW) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ if (!className.Equals(L"Shell_TrayWnd")) {
+ return false;
+ }
+ }
+ }
+
+ // Filter out layered windows that are not opaque or that set a transparency
+ // colorkey.
+ if (exStyles & WS_EX_LAYERED) {
+ BYTE alpha;
+ DWORD flags;
+
+ // GetLayeredWindowAttributes only works if the application has
+ // previously called SetLayeredWindowAttributes on the window.
+ // The function will fail if the layered window was setup with
+ // UpdateLayeredWindow. Treat this failure as the window being transparent.
+ // See Remarks section of
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlayeredwindowattributes
+ if (!::GetLayeredWindowAttributes(aHwnd, nullptr, &alpha, &flags)) {
+ return false;
+ }
+
+ if (flags & LWA_ALPHA && alpha < 255) {
+ return false;
+ }
+ if (flags & LWA_COLORKEY) {
+ return false;
+ }
+ }
+
+ // Filter out windows that do not have a simple rectangular region.
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ int result = GetWindowRgn(aHwnd, region);
+ ::DeleteObject(region);
+ if (result == COMPLEXREGION) {
+ return false;
+ }
+
+ // Windows 10 has cloaked windows, windows with WS_VISIBLE attribute but
+ // not displayed. explorer.exe, in particular has one that's the
+ // size of the desktop. It's usually behind Chrome windows in the z-order,
+ // but using a remote desktop can move it up in the z-order. So, ignore them.
+ DWORD reason;
+ if (SUCCEEDED(::DwmGetWindowAttribute(aHwnd, DWMWA_CLOAKED, &reason,
+ sizeof(reason))) &&
+ reason != 0) {
+ return false;
+ }
+
+ RECT winRect;
+ // Filter out windows that take up zero area. The call to GetWindowRect is one
+ // of the most expensive parts of this function, so it is last.
+ if (!::GetWindowRect(aHwnd, &winRect)) {
+ return false;
+ }
+ if (::IsRectEmpty(&winRect)) {
+ return false;
+ }
+
+ // Ignore popup windows since they're transient unless it is the Windows
+ // Taskbar
+ // XXX Chrome Widget popup handling is removed for now.
+ if (::GetWindowLong(aHwnd, GWL_STYLE) & WS_POPUP) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ if (!className.Equals(L"Shell_TrayWnd")) {
+ return false;
+ }
+ }
+ }
+
+ *aWindowRect = LayoutDeviceIntRect(winRect.left, winRect.top,
+ winRect.right - winRect.left,
+ winRect.bottom - winRect.top);
+
+ WINDOWPLACEMENT windowPlacement = {0};
+ windowPlacement.length = sizeof(WINDOWPLACEMENT);
+ ::GetWindowPlacement(aHwnd, &windowPlacement);
+ if (windowPlacement.showCmd == SW_MAXIMIZE) {
+ // If the window is maximized the window border extends beyond the visible
+ // region of the screen. Adjust the maximized window rect to fit the
+ // screen dimensions to ensure that fullscreen windows, which do not extend
+ // beyond the screen boundaries since they typically have no borders, will
+ // occlude maximized windows underneath them.
+ HMONITOR hmon = ::MonitorFromWindow(aHwnd, MONITOR_DEFAULTTONEAREST);
+ if (hmon) {
+ MONITORINFO mi;
+ mi.cbSize = sizeof(mi);
+ if (GetMonitorInfo(hmon, &mi)) {
+ LayoutDeviceIntRect workArea(mi.rcWork.left, mi.rcWork.top,
+ mi.rcWork.right - mi.rcWork.left,
+ mi.rcWork.bottom - mi.rcWork.top);
+ // Adjust aWindowRect to fit to monitor.
+ aWindowRect->width = std::min(workArea.width, aWindowRect->width);
+ if (aWindowRect->x < workArea.x) {
+ aWindowRect->x = workArea.x;
+ } else {
+ aWindowRect->x = std::min(workArea.x + workArea.width,
+ aWindowRect->x + aWindowRect->width) -
+ aWindowRect->width;
+ }
+ aWindowRect->height = std::min(workArea.height, aWindowRect->height);
+ if (aWindowRect->y < workArea.y) {
+ aWindowRect->y = workArea.y;
+ } else {
+ aWindowRect->y = std::min(workArea.y + workArea.height,
+ aWindowRect->y + aWindowRect->height) -
+ aWindowRect->height;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+// static
+void WinWindowOcclusionTracker::CallUpdateOcclusionState(
+ std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* tracker = WinWindowOcclusionTracker::Get();
+ if (!tracker) {
+ return;
+ }
+ tracker->UpdateOcclusionState(aMap, aShowAllWindows);
+}
+
+void WinWindowOcclusionTracker::UpdateOcclusionState(
+ std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+ LOG(LogLevel::Debug,
+ "WinWindowOcclusionTracker::UpdateOcclusionState() aShowAllWindows %d",
+ aShowAllWindows);
+
+ mNumVisibleRootWindows = 0;
+ for (auto& [hwnd, state] : *aMap) {
+ auto it = mHwndRootWindowMap.find(hwnd);
+ // The window was destroyed while processing occlusion.
+ if (it == mHwndRootWindowMap.end()) {
+ continue;
+ }
+ auto occlState = state;
+
+ // If the screen is locked or off, ignore occlusion state results and
+ // mark the window as occluded.
+ if (mScreenLocked || !mDisplayOn) {
+ occlState = OcclusionState::OCCLUDED;
+ } else if (aShowAllWindows) {
+ occlState = OcclusionState::VISIBLE;
+ }
+ nsCOMPtr<nsIWidget> widget = do_QueryReferent(it->second);
+ if (!widget) {
+ continue;
+ }
+ auto* baseWidget = static_cast<nsBaseWidget*>(widget.get());
+ baseWidget->NotifyOcclusionState(occlState);
+ if (baseWidget->SizeMode() != nsSizeMode_Minimized) {
+ mNumVisibleRootWindows++;
+ }
+ }
+}
+
+void WinWindowOcclusionTracker::OnSessionChange(WPARAM aStatusCode,
+ Maybe<bool> aIsCurrentSession) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aIsCurrentSession.isNothing() || !*aIsCurrentSession) {
+ return;
+ }
+
+ if (aStatusCode == WTS_SESSION_UNLOCK) {
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_UNLOCK");
+
+ // UNLOCK will cause a foreground window change, which will
+ // trigger an occlusion calculation on its own.
+ mScreenLocked = false;
+ } else if (aStatusCode == WTS_SESSION_LOCK) {
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_LOCK");
+
+ mScreenLocked = true;
+ MarkNonIconicWindowsOccluded();
+ }
+}
+
+void WinWindowOcclusionTracker::OnDisplayStateChanged(bool aDisplayOn) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnDisplayStateChanged() aDisplayOn %d",
+ aDisplayOn);
+
+ if (mDisplayOn == aDisplayOn) {
+ return;
+ }
+
+ mDisplayOn = aDisplayOn;
+ if (aDisplayOn) {
+ // Notify the window occlusion calculator of the display turning on
+ // which will schedule an occlusion calculation. This must be run
+ // on the WindowOcclusionCalculator thread.
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::HandleVisibilityChanged,
+ /* aVisible */ true);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+ } else {
+ MarkNonIconicWindowsOccluded();
+ }
+}
+
+void WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded()");
+
+ // Set all visible root windows as occluded. If not visible,
+ // set them as hidden.
+ for (auto& [hwnd, weak] : mHwndRootWindowMap) {
+ nsCOMPtr<nsIWidget> widget = do_QueryReferent(weak);
+ if (!widget) {
+ continue;
+ }
+ auto* baseWidget = static_cast<nsBaseWidget*>(widget.get());
+ auto state = (baseWidget->SizeMode() == nsSizeMode_Minimized)
+ ? OcclusionState::HIDDEN
+ : OcclusionState::OCCLUDED;
+ baseWidget->NotifyOcclusionState(state);
+ }
+}
+
+void WinWindowOcclusionTracker::TriggerCalculation() {
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::HandleTriggerCalculation);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+// static
+BOOL WinWindowOcclusionTracker::DumpOccludingWindowsCallback(HWND aHWnd,
+ LPARAM aLParam) {
+ HWND hwnd = reinterpret_cast<HWND>(aLParam);
+
+ LayoutDeviceIntRect windowRect;
+ bool windowIsOccluding = IsWindowVisibleAndFullyOpaque(aHWnd, &windowRect);
+ if (windowIsOccluding) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHWnd, className)) {
+ const auto name = NS_ConvertUTF16toUTF8(className);
+ printf_stderr(
+ "DumpOccludingWindowsCallback() aHWnd %p className %s windowRect(%d, "
+ "%d, %d, %d)\n",
+ aHWnd, name.get(), windowRect.x, windowRect.y, windowRect.width,
+ windowRect.height);
+ }
+ }
+
+ if (aHWnd == hwnd) {
+ return false;
+ }
+ return true;
+}
+
+void WinWindowOcclusionTracker::DumpOccludingWindows(HWND aHWnd) {
+ printf_stderr("DumpOccludingWindows() until aHWnd %p visible %d iconic %d\n",
+ aHWnd, ::IsWindowVisible(aHWnd), ::IsIconic(aHWnd));
+ ::EnumWindows(&DumpOccludingWindowsCallback, reinterpret_cast<LPARAM>(aHWnd));
+}
+
+// static
+StaticRefPtr<WinWindowOcclusionTracker::WindowOcclusionCalculator>
+ WinWindowOcclusionTracker::WindowOcclusionCalculator::sCalculator;
+
+WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ WindowOcclusionCalculator()
+ : mMonitor(WinWindowOcclusionTracker::Get()->mMonitor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WindowOcclusionCalculator()");
+
+ mSerializedTaskDispatcher =
+ WinWindowOcclusionTracker::Get()->GetSerializedTaskDispatcher();
+}
+
+WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ~WindowOcclusionCalculator() {}
+
+// static
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::CreateInstance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sCalculator = new WindowOcclusionCalculator();
+}
+
+// static
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::ClearInstance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sCalculator = nullptr;
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::Initialize() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(!mVirtualDesktopManager);
+ CALC_LOG(LogLevel::Info, "Initialize()");
+
+#ifndef __MINGW32__
+ if (!IsWin10OrLater()) {
+ return;
+ }
+
+ RefPtr<IVirtualDesktopManager> desktopManager;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
+ __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
+ if (FAILED(hr)) {
+ return;
+ }
+ mVirtualDesktopManager = desktopManager;
+#endif
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::Shutdown() {
+ MonitorAutoLock lock(mMonitor);
+
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "Shutdown()");
+
+ UnregisterEventHooks();
+ if (mOcclusionUpdateRunnable) {
+ mOcclusionUpdateRunnable->Cancel();
+ mOcclusionUpdateRunnable = nullptr;
+ }
+ mVirtualDesktopManager = nullptr;
+
+ mMonitor.NotifyAll();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ EnableOcclusionTrackingForWindow(HWND aHwnd) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+ CALC_LOG(LogLevel::Info, "EnableOcclusionTrackingForWindow() aHwnd %p",
+ aHwnd);
+
+ MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState.find(aHwnd) ==
+ mRootWindowHwndsOcclusionState.end());
+ mRootWindowHwndsOcclusionState[aHwnd] = OcclusionState::UNKNOWN;
+
+ if (mGlobalEventHooks.empty()) {
+ RegisterEventHooks();
+ }
+
+ // Schedule an occlusion calculation so that the newly tracked window does
+ // not have a stale occlusion status.
+ ScheduleOcclusionCalculationIfNeeded();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ DisableOcclusionTrackingForWindow(HWND aHwnd) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+ CALC_LOG(LogLevel::Info, "DisableOcclusionTrackingForWindow() aHwnd %p",
+ aHwnd);
+
+ MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState.find(aHwnd) !=
+ mRootWindowHwndsOcclusionState.end());
+ mRootWindowHwndsOcclusionState.erase(aHwnd);
+
+ if (mMovingWindow == aHwnd) {
+ mMovingWindow = 0;
+ }
+
+ if (mRootWindowHwndsOcclusionState.empty()) {
+ UnregisterEventHooks();
+ if (mOcclusionUpdateRunnable) {
+ mOcclusionUpdateRunnable->Cancel();
+ mOcclusionUpdateRunnable = nullptr;
+ }
+ }
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ HandleVisibilityChanged(bool aVisible) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "HandleVisibilityChange() aVisible %d", aVisible);
+
+ // May have gone from having no visible windows to having one, in
+ // which case we need to register event hooks, and make sure that an
+ // occlusion calculation is scheduled.
+ if (aVisible) {
+ MaybeRegisterEventHooks();
+ ScheduleOcclusionCalculationIfNeeded();
+ }
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ HandleTriggerCalculation() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "HandleTriggerCalculation()");
+
+ MaybeRegisterEventHooks();
+ ScheduleOcclusionCalculationIfNeeded();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ MaybeRegisterEventHooks() {
+ if (mGlobalEventHooks.empty()) {
+ RegisterEventHooks();
+ }
+}
+
+// static
+void CALLBACK
+WinWindowOcclusionTracker::WindowOcclusionCalculator::EventHookCallback(
+ HWINEVENTHOOK aWinEventHook, DWORD aEvent, HWND aHwnd, LONG aIdObject,
+ LONG aIdChild, DWORD aEventThread, DWORD aMsEventTime) {
+ if (sCalculator) {
+ sCalculator->ProcessEventHookCallback(aWinEventHook, aEvent, aHwnd,
+ aIdObject, aIdChild);
+ }
+}
+
+// static
+BOOL CALLBACK WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ComputeNativeWindowOcclusionStatusCallback(HWND aHwnd, LPARAM aLParam) {
+ if (sCalculator) {
+ return sCalculator->ProcessComputeNativeWindowOcclusionStatusCallback(
+ aHwnd, reinterpret_cast<std::unordered_set<DWORD>*>(aLParam));
+ }
+ return FALSE;
+}
+
+// static
+BOOL CALLBACK WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ UpdateVisibleWindowProcessIdsCallback(HWND aHwnd, LPARAM aLParam) {
+ if (sCalculator) {
+ sCalculator->ProcessUpdateVisibleWindowProcessIdsCallback(aHwnd);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ UpdateVisibleWindowProcessIds() {
+ mPidsForLocationChangeHook.clear();
+ ::EnumWindows(&UpdateVisibleWindowProcessIdsCallback, 0);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ComputeNativeWindowOcclusionStatus() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+
+ if (mOcclusionUpdateRunnable) {
+ mOcclusionUpdateRunnable = nullptr;
+ }
+
+ if (mRootWindowHwndsOcclusionState.empty()) {
+ return;
+ }
+
+ // Set up initial conditions for occlusion calculation.
+ bool shouldUnregisterEventHooks = true;
+
+ // Compute the LayoutDeviceIntRegion for the screen.
+ int screenLeft = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+ int screenTop = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
+ int screenWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ int screenHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ LayoutDeviceIntRegion screenRegion =
+ LayoutDeviceIntRect(screenLeft, screenTop, screenWidth, screenHeight);
+ mNumRootWindowsWithUnknownOcclusionState = 0;
+
+ CALC_LOG(LogLevel::Debug,
+ "ComputeNativeWindowOcclusionStatus() screen(%d, %d, %d, %d)",
+ screenLeft, screenTop, screenWidth, screenHeight);
+
+ for (auto& [hwnd, state] : mRootWindowHwndsOcclusionState) {
+ // IsIconic() checks for a minimized window. Immediately set the state of
+ // minimized windows to HIDDEN.
+ if (::IsIconic(hwnd)) {
+ state = OcclusionState::HIDDEN;
+ } else if (IsWindowOnCurrentVirtualDesktop(hwnd) == Some(false)) {
+ // If window is not on the current virtual desktop, immediately
+ // set the state of the window to OCCLUDED.
+ state = OcclusionState::OCCLUDED;
+ // Don't unregister event hooks when not on current desktop. There's no
+ // notification when that changes, so we can't reregister event hooks.
+ shouldUnregisterEventHooks = false;
+ } else {
+ state = OcclusionState::UNKNOWN;
+ shouldUnregisterEventHooks = false;
+ mNumRootWindowsWithUnknownOcclusionState++;
+ }
+ }
+
+ // Unregister event hooks if all native windows are minimized.
+ if (shouldUnregisterEventHooks) {
+ UnregisterEventHooks();
+ } else {
+ std::unordered_set<DWORD> currentPidsWithVisibleWindows;
+ mUnoccludedDesktopRegion = screenRegion;
+ // Calculate unoccluded region if there is a non-minimized native window.
+ // Also compute |current_pids_with_visible_windows| as we enumerate
+ // the windows.
+ EnumWindows(&ComputeNativeWindowOcclusionStatusCallback,
+ reinterpret_cast<LPARAM>(&currentPidsWithVisibleWindows));
+ // Check if mPidsForLocationChangeHook has any pids of processes
+ // currently without visible windows. If so, unhook the win event,
+ // remove the pid from mPidsForLocationChangeHook and remove
+ // the corresponding event hook from mProcessEventHooks.
+ std::unordered_set<DWORD> pidsToRemove;
+ for (auto locChangePid : mPidsForLocationChangeHook) {
+ if (currentPidsWithVisibleWindows.find(locChangePid) ==
+ currentPidsWithVisibleWindows.end()) {
+ // Remove the event hook from our map, and unregister the event hook.
+ // It's possible the eventhook will no longer be valid, but if we don't
+ // unregister the event hook, a process that toggles between having
+ // visible windows and not having visible windows could cause duplicate
+ // event hooks to get registered for the process.
+ UnhookWinEvent(mProcessEventHooks[locChangePid]);
+ mProcessEventHooks.erase(locChangePid);
+ pidsToRemove.insert(locChangePid);
+ }
+ }
+ if (!pidsToRemove.empty()) {
+ // XXX simplify
+ for (auto it = mPidsForLocationChangeHook.begin();
+ it != mPidsForLocationChangeHook.end();) {
+ if (pidsToRemove.find(*it) != pidsToRemove.end()) {
+ it = mPidsForLocationChangeHook.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ }
+
+ std::unordered_map<HWND, OcclusionState>* map =
+ &mRootWindowHwndsOcclusionState;
+ bool showAllWindows = mShowingThumbnails;
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "CallUpdateOcclusionState", [map, showAllWindows]() {
+ WinWindowOcclusionTracker::CallUpdateOcclusionState(map,
+ showAllWindows);
+ });
+ mSerializedTaskDispatcher->PostTaskToMain(runnable.forget());
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ScheduleOcclusionCalculationIfNeeded() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+
+ // OcclusionUpdateRunnable is already queued.
+ if (mOcclusionUpdateRunnable) {
+ return;
+ }
+
+ CALC_LOG(LogLevel::Debug, "ScheduleOcclusionCalculationIfNeeded()");
+
+ RefPtr<CancelableRunnable> task = new OcclusionUpdateRunnable(this);
+ mOcclusionUpdateRunnable = task;
+ mSerializedTaskDispatcher->PostDelayedTaskToCalculator(
+ task.forget(), kOcclusionUpdateRunnableDelayMs);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ RegisterGlobalEventHook(DWORD aEventMin, DWORD aEventMax) {
+ HWINEVENTHOOK eventHook =
+ ::SetWinEventHook(aEventMin, aEventMax, nullptr, &EventHookCallback, 0, 0,
+ WINEVENT_OUTOFCONTEXT);
+ mGlobalEventHooks.push_back(eventHook);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ RegisterEventHookForProcess(DWORD aPid) {
+ mPidsForLocationChangeHook.insert(aPid);
+ mProcessEventHooks[aPid] = SetWinEventHook(
+ EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, nullptr,
+ &EventHookCallback, aPid, 0, WINEVENT_OUTOFCONTEXT);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ RegisterEventHooks() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_RELEASE_ASSERT(mGlobalEventHooks.empty());
+ CALC_LOG(LogLevel::Info, "RegisterEventHooks()");
+
+ // Detects native window lost mouse capture
+ RegisterGlobalEventHook(EVENT_SYSTEM_CAPTUREEND, EVENT_SYSTEM_CAPTUREEND);
+
+ // Detects native window move (drag) and resizing events.
+ RegisterGlobalEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND);
+
+ // Detects native window minimize and restore from taskbar events.
+ RegisterGlobalEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND);
+
+ // Detects foreground window changing.
+ RegisterGlobalEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND);
+
+ // Detects objects getting shown and hidden. Used to know when the task bar
+ // and alt tab are showing preview windows so we can unocclude windows.
+ RegisterGlobalEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE);
+
+ // Detects object state changes, e.g., enable/disable state, native window
+ // maximize and native window restore events.
+ RegisterGlobalEventHook(EVENT_OBJECT_STATECHANGE, EVENT_OBJECT_STATECHANGE);
+
+ // Cloaking and uncloaking of windows should trigger an occlusion calculation.
+ // In particular, switching virtual desktops seems to generate these events.
+ RegisterGlobalEventHook(EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED);
+
+ // Determine which subset of processes to set EVENT_OBJECT_LOCATIONCHANGE on
+ // because otherwise event throughput is very high, as it generates events
+ // for location changes of all objects, including the mouse moving on top of a
+ // window.
+ UpdateVisibleWindowProcessIds();
+ for (DWORD pid : mPidsForLocationChangeHook) {
+ RegisterEventHookForProcess(pid);
+ }
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ UnregisterEventHooks() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "UnregisterEventHooks()");
+
+ for (const auto eventHook : mGlobalEventHooks) {
+ ::UnhookWinEvent(eventHook);
+ }
+ mGlobalEventHooks.clear();
+
+ for (const auto& [pid, eventHook] : mProcessEventHooks) {
+ ::UnhookWinEvent(eventHook);
+ }
+ mProcessEventHooks.clear();
+
+ mPidsForLocationChangeHook.clear();
+}
+
+bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ProcessComputeNativeWindowOcclusionStatusCallback(
+ HWND aHwnd, std::unordered_set<DWORD>* aCurrentPidsWithVisibleWindows) {
+ LayoutDeviceIntRegion currUnoccludedDestkop = mUnoccludedDesktopRegion;
+ LayoutDeviceIntRect windowRect;
+ bool windowIsOccluding =
+ WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd, &windowRect);
+ if (windowIsOccluding) {
+ // Hook this window's process with EVENT_OBJECT_LOCATION_CHANGE, if we are
+ // not already doing so.
+ DWORD pid;
+ ::GetWindowThreadProcessId(aHwnd, &pid);
+ aCurrentPidsWithVisibleWindows->insert(pid);
+ auto it = mProcessEventHooks.find(pid);
+ if (it == mProcessEventHooks.end()) {
+ RegisterEventHookForProcess(pid);
+ }
+
+ // If no more root windows to consider, return true so we can continue
+ // looking for windows we haven't hooked.
+ if (mNumRootWindowsWithUnknownOcclusionState == 0) {
+ return true;
+ }
+
+ mUnoccludedDesktopRegion.SubOut(windowRect);
+ } else if (mNumRootWindowsWithUnknownOcclusionState == 0) {
+ // This window can't occlude other windows, but we've determined the
+ // occlusion state of all root windows, so we can return.
+ return true;
+ }
+
+ // Ignore moving windows when deciding if windows under it are occluded.
+ if (aHwnd == mMovingWindow) {
+ return true;
+ }
+
+ // Check if |hwnd| is a root window; if so, we're done figuring out
+ // if it's occluded because we've seen all the windows "over" it.
+ auto it = mRootWindowHwndsOcclusionState.find(aHwnd);
+ if (it == mRootWindowHwndsOcclusionState.end() ||
+ it->second != OcclusionState::UNKNOWN) {
+ return true;
+ }
+
+ CALC_LOG(LogLevel::Debug,
+ "ProcessComputeNativeWindowOcclusionStatusCallback() windowRect(%d, "
+ "%d, %d, %d) IsOccluding %d",
+ windowRect.x, windowRect.y, windowRect.width, windowRect.height,
+ windowIsOccluding);
+
+ // On Win7, default theme makes root windows have complex regions by
+ // default. But we can still check if their bounding rect is occluded.
+ if (!windowIsOccluding) {
+ RECT rect;
+ if (::GetWindowRect(aHwnd, &rect) != 0) {
+ LayoutDeviceIntRect windowRect(
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+ currUnoccludedDestkop.SubOut(windowRect);
+ }
+ }
+
+ it->second = (mUnoccludedDesktopRegion == currUnoccludedDestkop)
+ ? OcclusionState::OCCLUDED
+ : OcclusionState::VISIBLE;
+ mNumRootWindowsWithUnknownOcclusionState--;
+
+ return true;
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
+ HWND aHwnd, LONG aIdObject, LONG aIdChild) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+
+ // No need to calculate occlusion if a zero HWND generated the event. This
+ // happens if there is no window associated with the event, e.g., mouse move
+ // events.
+ if (!aHwnd) {
+ return;
+ }
+
+ // We only care about events for window objects. In particular, we don't care
+ // about OBJID_CARET, which is spammy.
+ if (aIdObject != OBJID_WINDOW) {
+ return;
+ }
+
+ CALC_LOG(LogLevel::Debug,
+ "WindowOcclusionCalculator::ProcessEventHookCallback() aEvent 0x%lx",
+ aEvent);
+
+ // We generally ignore events for popup windows, except for when the taskbar
+ // is hidden or Windows Taskbar, in which case we recalculate occlusion.
+ // XXX Chrome Widget popup handling is removed for now.
+ bool calculateOcclusion = true;
+ if (::GetWindowLong(aHwnd, GWL_STYLE) & WS_POPUP) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ calculateOcclusion = className.Equals(L"Shell_TrayWnd");
+ }
+ }
+
+ // Detect if either the alt tab view or the task list thumbnail is being
+ // shown. If so, mark all non-hidden windows as occluded, and remember that
+ // we're in the showing_thumbnails state. This lasts until we get told that
+ // either the alt tab view or task list thumbnail are hidden.
+ if (aEvent == EVENT_OBJECT_SHOW) {
+ // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
+ // needed.
+ if (mShowingThumbnails) {
+ return;
+ }
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ const auto name = NS_ConvertUTF16toUTF8(className);
+ CALC_LOG(LogLevel::Debug,
+ "ProcessEventHookCallback() EVENT_OBJECT_SHOW %s", name.get());
+
+ if (name.Equals("MultitaskingViewFrame") ||
+ name.Equals("TaskListThumbnailWnd")) {
+ CALC_LOG(LogLevel::Info,
+ "ProcessEventHookCallback() mShowingThumbnails = true");
+ mShowingThumbnails = true;
+
+ std::unordered_map<HWND, OcclusionState>* map =
+ &mRootWindowHwndsOcclusionState;
+ bool showAllWindows = mShowingThumbnails;
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "CallUpdateOcclusionState", [map, showAllWindows]() {
+ WinWindowOcclusionTracker::CallUpdateOcclusionState(
+ map, showAllWindows);
+ });
+ mSerializedTaskDispatcher->PostTaskToMain(runnable.forget());
+ }
+ }
+ return;
+ } else if (aEvent == EVENT_OBJECT_HIDE) {
+ // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
+ // needed.
+ if (!mShowingThumbnails) {
+ return;
+ }
+ nsAutoString className;
+ WinUtils::GetClassName(aHwnd, className);
+ const auto name = NS_ConvertUTF16toUTF8(className);
+ CALC_LOG(LogLevel::Debug, "ProcessEventHookCallback() EVENT_OBJECT_HIDE %s",
+ name.get());
+ if (name.Equals("MultitaskingViewFrame") ||
+ name.Equals("TaskListThumbnailWnd")) {
+ CALC_LOG(LogLevel::Info,
+ "ProcessEventHookCallback() mShowingThumbnails = false");
+ mShowingThumbnails = false;
+ // Let occlusion calculation fix occlusion state, even though hwnd might
+ // be a popup window.
+ calculateOcclusion = true;
+ } else {
+ return;
+ }
+ }
+ // Don't continually calculate occlusion while a window is moving (unless it's
+ // a root window), but instead once at the beginning and once at the end.
+ // Remember the window being moved so if it's a root window, we can ignore
+ // it when deciding if windows under it are occluded.
+ else if (aEvent == EVENT_SYSTEM_MOVESIZESTART) {
+ mMovingWindow = aHwnd;
+ } else if (aEvent == EVENT_SYSTEM_MOVESIZEEND) {
+ mMovingWindow = 0;
+ } else if (mMovingWindow != 0) {
+ if (aEvent == EVENT_OBJECT_LOCATIONCHANGE ||
+ aEvent == EVENT_OBJECT_STATECHANGE) {
+ // Ignore move events if it's not a root window that's being moved. If it
+ // is a root window, we want to calculate occlusion to support tab
+ // dragging to windows that were occluded when the drag was started but
+ // are no longer occluded.
+ if (mRootWindowHwndsOcclusionState.find(aHwnd) ==
+ mRootWindowHwndsOcclusionState.end()) {
+ return;
+ }
+ } else {
+ // If we get an event that isn't a location/state change, then we probably
+ // missed the movesizeend notification, or got events out of order. In
+ // that case, we want to go back to normal occlusion calculation.
+ mMovingWindow = 0;
+ }
+ }
+
+ if (!calculateOcclusion) {
+ return;
+ }
+
+ ScheduleOcclusionCalculationIfNeeded();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+
+ LayoutDeviceIntRect windowRect;
+ if (WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd, &windowRect)) {
+ DWORD pid;
+ ::GetWindowThreadProcessId(aHwnd, &pid);
+ mPidsForLocationChangeHook.insert(pid);
+ }
+}
+
+bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
+ HWND aHwnd, LayoutDeviceIntRect* aWindowRect) {
+ return IsWindowVisibleAndFullyOpaque(aHwnd, aWindowRect) &&
+ (IsWindowOnCurrentVirtualDesktop(aHwnd) == Some(true));
+}
+
+Maybe<bool> WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ IsWindowOnCurrentVirtualDesktop(HWND aHwnd) {
+ if (!mVirtualDesktopManager) {
+ return Some(true);
+ }
+
+ BOOL onCurrentDesktop;
+ HRESULT hr = mVirtualDesktopManager->IsWindowOnCurrentVirtualDesktop(
+ aHwnd, &onCurrentDesktop);
+ if (FAILED(hr)) {
+ // In this case, we do not know the window is in which virtual desktop.
+ return Nothing();
+ }
+
+ if (onCurrentDesktop) {
+ return Some(true);
+ }
+
+ GUID workspaceGuid;
+ hr = mVirtualDesktopManager->GetWindowDesktopId(aHwnd, &workspaceGuid);
+ if (FAILED(hr)) {
+ // In this case, we do not know the window is in which virtual desktop.
+ return Nothing();
+ }
+
+ // IsWindowOnCurrentVirtualDesktop() is flaky for newly opened windows,
+ // which causes test flakiness. Occasionally, it incorrectly says a window
+ // is not on the current virtual desktop when it is. In this situation,
+ // it also returns GUID_NULL for the desktop id.
+ if (workspaceGuid == GUID_NULL) {
+ // In this case, we do not know if the window is in which virtual desktop.
+ // But we hanle it as on current virtual desktop.
+ // It does not cause a problem to window occlusion.
+ // Since if window is not on current virtual desktop, window size becomes
+ // (0, 0, 0, 0). It makes window occlusion handling explicit. It is
+ // necessary for gtest.
+ return Some(true);
+ }
+
+ return Some(false);
+}
+
+#undef LOG
+#undef CALC_LOG
+
+} // namespace mozilla::widget