2021-10-06 07:21:59 +00:00
|
|
|
/* -*- 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"
|
2021-10-08 13:11:52 +00:00
|
|
|
#include "gfxConfig.h"
|
2021-10-06 07:21:59 +00:00
|
|
|
#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"
|
2022-02-15 14:33:06 +00:00
|
|
|
#include "nsWindow.h"
|
2021-10-06 07:21:59 +00:00
|
|
|
#include "transport/runnable_utils.h"
|
Bug 1915665 - [1/2] Streamline WinEventHub r=win-reviewers,gfx-reviewers,handyman,bradwerth
`WinEventHub` was largely taken from Chromium, where its dynamic
connection/disconnection features are presumably more used. We don't
have any need for that, and anyway if we ever do it'll be easy to add
it alongside the static functionality or even migrate the latter over.
Hardwire the relevant message-processing and invocation directly into
the hidden window's WNDPROC, rather than providing a registration
system. Following the discoveries made in bug 1571516, also remove the
additional failure-mode checks added in bug 1852801, reverting to a
crash (which can be unified with 1571516 when it shows up).
This has, as fallout, a minor functional change to the occlusion
tracker: the subfeature prefs mentioned above are now checked at
message-receipt time, rather than at construction time.
Differential Revision: https://phabricator.services.mozilla.com/D220639
2024-09-17 19:16:46 +00:00
|
|
|
#include "WinEventObserver.h"
|
2021-10-06 07:21:59 +00:00
|
|
|
#include "WinUtils.h"
|
|
|
|
|
|
|
|
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:
|
2022-02-15 14:33:06 +00:00
|
|
|
friend class DelayedTaskRunnable;
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
~SerializedTaskDispatcher();
|
|
|
|
|
2021-10-12 21:51:41 +00:00
|
|
|
struct Data {
|
2021-12-07 22:31:36 +00:00
|
|
|
std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
|
|
|
|
mTasks;
|
2021-10-12 21:51:41 +00:00
|
|
|
bool mDestroyed = false;
|
2021-12-02 04:03:14 +00:00
|
|
|
RefPtr<Runnable> mCurrentRunnable;
|
2021-10-12 21:51:41 +00:00
|
|
|
};
|
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
void PostTasksIfNecessary(nsISerialEventTarget* aEventTarget,
|
|
|
|
const DataMutex<Data>::AutoLock& aProofOfLock);
|
2021-10-06 07:21:59 +00:00
|
|
|
void HandleDelayedTask(already_AddRefed<nsIRunnable> aTask);
|
|
|
|
void HandleTasks();
|
|
|
|
|
|
|
|
// Hold current EventTarget during calling nsIRunnable::Run().
|
2021-12-07 22:31:36 +00:00
|
|
|
RefPtr<nsISerialEventTarget> mCurrentEventTarget = nullptr;
|
2021-10-06 07:21:59 +00:00
|
|
|
|
|
|
|
DataMutex<Data> mData;
|
|
|
|
};
|
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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;
|
2021-12-07 22:31:36 +00:00
|
|
|
std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
|
|
|
|
empty;
|
2021-10-06 07:21:59 +00:00
|
|
|
std::swap(data->mTasks, empty);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SerializedTaskDispatcher::PostTaskToMain(
|
|
|
|
already_AddRefed<nsIRunnable> aTask) {
|
2022-03-02 11:20:38 +00:00
|
|
|
RefPtr<nsIRunnable> task = aTask;
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
auto data = mData.Lock();
|
|
|
|
if (data->mDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsISerialEventTarget* eventTarget = GetMainThreadSerialEventTarget();
|
2022-03-02 11:20:38 +00:00
|
|
|
data->mTasks.push({std::move(task), eventTarget});
|
2021-12-02 04:03:14 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
|
|
|
|
PostTasksIfNecessary(eventTarget, data);
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SerializedTaskDispatcher::PostTaskToCalculator(
|
|
|
|
already_AddRefed<nsIRunnable> aTask) {
|
2022-03-02 11:20:38 +00:00
|
|
|
RefPtr<nsIRunnable> task = aTask;
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
auto data = mData.Lock();
|
|
|
|
if (data->mDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsISerialEventTarget* eventTarget =
|
|
|
|
WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
|
2022-03-02 11:20:38 +00:00
|
|
|
data->mTasks.push({std::move(task), eventTarget});
|
2021-12-02 04:03:14 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
|
|
|
|
PostTasksIfNecessary(eventTarget, data);
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SerializedTaskDispatcher::PostDelayedTaskToCalculator(
|
|
|
|
already_AddRefed<Runnable> aTask, int aDelayMs) {
|
|
|
|
CALC_LOG(LogLevel::Debug,
|
|
|
|
"SerializedTaskDispatcher::PostDelayedTaskToCalculator()");
|
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
RefPtr<DelayedTaskRunnable> runnable =
|
|
|
|
new DelayedTaskRunnable(this, std::move(aTask));
|
2021-10-06 07:21:59 +00:00
|
|
|
MessageLoop* targetLoop =
|
|
|
|
WinWindowOcclusionTracker::OcclusionCalculatorLoop();
|
|
|
|
targetLoop->PostDelayedTask(runnable.forget(), aDelayMs);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SerializedTaskDispatcher::IsOnCurrentThread() {
|
|
|
|
return !!mCurrentEventTarget;
|
|
|
|
}
|
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
void SerializedTaskDispatcher::PostTasksIfNecessary(
|
2021-10-12 21:51:41 +00:00
|
|
|
nsISerialEventTarget* aEventTarget,
|
|
|
|
const DataMutex<Data>::AutoLock& aProofOfLock) {
|
2021-12-02 04:03:14 +00:00
|
|
|
MOZ_ASSERT(!aProofOfLock->mTasks.empty());
|
|
|
|
|
|
|
|
if (aProofOfLock->mCurrentRunnable) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
RefPtr<Runnable> runnable =
|
|
|
|
WrapRunnable(RefPtr<SerializedTaskDispatcher>(this),
|
|
|
|
&SerializedTaskDispatcher::HandleTasks);
|
2021-12-02 04:03:14 +00:00
|
|
|
aProofOfLock->mCurrentRunnable = runnable;
|
2021-10-06 07:21:59 +00:00
|
|
|
aEventTarget->Dispatch(runnable.forget());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SerializedTaskDispatcher::HandleDelayedTask(
|
|
|
|
already_AddRefed<nsIRunnable> aTask) {
|
|
|
|
MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
|
|
|
|
CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleDelayedTask()");
|
|
|
|
|
2022-03-02 11:20:38 +00:00
|
|
|
RefPtr<nsIRunnable> task = aTask;
|
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
auto data = mData.Lock();
|
|
|
|
if (data->mDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsISerialEventTarget* eventTarget =
|
|
|
|
WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
|
2022-03-02 11:20:38 +00:00
|
|
|
data->mTasks.push({std::move(task), eventTarget});
|
2021-12-02 04:03:14 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
|
|
|
|
PostTasksIfNecessary(eventTarget, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SerializedTaskDispatcher::HandleTasks() {
|
2021-12-07 22:31:36 +00:00
|
|
|
RefPtr<nsIRunnable> frontTask;
|
2021-12-02 04:03:14 +00:00
|
|
|
|
|
|
|
// Get front task
|
2021-10-06 07:21:59 +00:00
|
|
|
{
|
|
|
|
auto data = mData.Lock();
|
|
|
|
if (data->mDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
2021-12-02 04:03:14 +00:00
|
|
|
MOZ_RELEASE_ASSERT(data->mCurrentRunnable);
|
|
|
|
MOZ_RELEASE_ASSERT(!data->mTasks.empty());
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2021-12-07 22:31:36 +00:00
|
|
|
frontTask = data->mTasks.front().first;
|
2021-10-12 21:51:41 +00:00
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
MOZ_RELEASE_ASSERT(!mCurrentEventTarget);
|
2021-12-07 22:31:36 +00:00
|
|
|
mCurrentEventTarget = data->mTasks.front().second;
|
2021-12-02 04:03:14 +00:00
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
while (frontTask) {
|
2021-10-06 07:21:59 +00:00
|
|
|
if (NS_IsMainThread()) {
|
|
|
|
LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
|
|
|
|
} else {
|
|
|
|
CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT_IF(NS_IsMainThread(),
|
2021-12-07 22:31:36 +00:00
|
|
|
mCurrentEventTarget == GetMainThreadSerialEventTarget());
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT_IF(
|
|
|
|
!NS_IsMainThread(),
|
2021-12-07 22:31:36 +00:00
|
|
|
mCurrentEventTarget == MessageLoop::current()->SerialEventTarget());
|
2021-10-06 07:21:59 +00:00
|
|
|
|
|
|
|
frontTask->Run();
|
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
// Get next task
|
|
|
|
{
|
|
|
|
auto data = mData.Lock();
|
2022-02-15 14:33:06 +00:00
|
|
|
if (data->mDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
2021-12-02 04:03:14 +00:00
|
|
|
|
|
|
|
frontTask = nullptr;
|
|
|
|
data->mTasks.pop();
|
|
|
|
// Check if next task could be handled on current thread
|
|
|
|
if (!data->mTasks.empty() &&
|
2021-12-07 22:31:36 +00:00
|
|
|
data->mTasks.front().second == mCurrentEventTarget) {
|
|
|
|
frontTask = data->mTasks.front().first;
|
2021-12-02 04:03:14 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-12 21:51:41 +00:00
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
MOZ_ASSERT(!frontTask);
|
2021-10-12 21:51:41 +00:00
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
// Post tasks to different thread if pending tasks exist.
|
|
|
|
{
|
|
|
|
auto data = mData.Lock();
|
|
|
|
data->mCurrentRunnable = nullptr;
|
|
|
|
mCurrentEventTarget = nullptr;
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2021-12-02 04:03:14 +00:00
|
|
|
if (data->mDestroyed || data->mTasks.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
2021-10-12 21:51:41 +00:00
|
|
|
|
2021-12-07 22:31:36 +00:00
|
|
|
PostTasksIfNecessary(data->mTasks.front().second, data);
|
2021-10-12 21:51:41 +00:00
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
StaticRefPtr<WinWindowOcclusionTracker> WinWindowOcclusionTracker::sTracker;
|
|
|
|
|
|
|
|
/* static */
|
2021-11-18 14:19:59 +00:00
|
|
|
WinWindowOcclusionTracker* WinWindowOcclusionTracker::Get() {
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2023-01-27 23:19:08 +00:00
|
|
|
if (!sTracker || sTracker->mHasAttemptedShutdown) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
return sTracker;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2021-10-26 02:37:42 +00:00
|
|
|
void WinWindowOcclusionTracker::Ensure() {
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2021-10-26 02:37:42 +00:00
|
|
|
LOG(LogLevel::Info, "WinWindowOcclusionTracker::Ensure()");
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
base::Thread::Options options;
|
|
|
|
options.message_loop_type = MessageLoop::TYPE_UI;
|
|
|
|
|
2021-10-26 02:37:42 +00:00
|
|
|
if (sTracker) {
|
2023-01-27 23:19:08 +00:00
|
|
|
// Try to reuse the thread, which involves stopping and restarting it.
|
|
|
|
sTracker->mThread->Stop();
|
|
|
|
if (sTracker->mThread->StartWithOptions(options)) {
|
|
|
|
// Success!
|
|
|
|
sTracker->mHasAttemptedShutdown = false;
|
|
|
|
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;
|
2021-10-26 02:37:42 +00:00
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
UniquePtr<base::Thread> thread =
|
|
|
|
MakeUnique<base::Thread>("WinWindowOcclusionCalc");
|
2021-10-06 07:21:59 +00:00
|
|
|
|
|
|
|
if (!thread->StartWithOptions(options)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
sTracker = new WinWindowOcclusionTracker(std::move(thread));
|
2021-10-06 07:21:59 +00:00
|
|
|
WindowOcclusionCalculator::CreateInstance();
|
2022-02-15 14:33:06 +00:00
|
|
|
|
|
|
|
RefPtr<Runnable> runnable =
|
|
|
|
WrapRunnable(RefPtr<WindowOcclusionCalculator>(
|
|
|
|
WindowOcclusionCalculator::GetInstance()),
|
|
|
|
&WindowOcclusionCalculator::Initialize);
|
|
|
|
sTracker->mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
void WinWindowOcclusionTracker::ShutDown() {
|
2023-01-24 02:12:03 +00:00
|
|
|
if (!sTracker) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
LOG(LogLevel::Info, "WinWindowOcclusionTracker::ShutDown()");
|
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
sTracker->mHasAttemptedShutdown = true;
|
2021-10-06 07:21:59 +00:00
|
|
|
sTracker->Destroy();
|
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
// 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));
|
2022-11-08 15:53:35 +00:00
|
|
|
}
|
2022-02-15 14:33:06 +00:00
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
if (status == CVStatus::NoTimeout) {
|
|
|
|
WindowOcclusionCalculator::ClearInstance();
|
|
|
|
sTracker = nullptr;
|
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void WinWindowOcclusionTracker::Destroy() {
|
|
|
|
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::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;
|
|
|
|
}
|
|
|
|
|
2022-01-12 00:12:18 +00:00
|
|
|
nsWeakPtr weak = do_GetWeakReference(aWindow);
|
|
|
|
mHwndRootWindowMap.emplace(aHwnd, weak);
|
2021-10-06 07:21:59 +00:00
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
WinWindowOcclusionTracker::WinWindowOcclusionTracker(
|
|
|
|
UniquePtr<base::Thread> aThread)
|
|
|
|
: mThread(std::move(aThread)), mMonitor("WinWindowOcclusionTracker") {
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
LOG(LogLevel::Info, "WinWindowOcclusionTracker::WinWindowOcclusionTracker()");
|
|
|
|
|
Bug 1915665 - [1/2] Streamline WinEventHub r=win-reviewers,gfx-reviewers,handyman,bradwerth
`WinEventHub` was largely taken from Chromium, where its dynamic
connection/disconnection features are presumably more used. We don't
have any need for that, and anyway if we ever do it'll be easy to add
it alongside the static functionality or even migrate the latter over.
Hardwire the relevant message-processing and invocation directly into
the hidden window's WNDPROC, rather than providing a registration
system. Following the discoveries made in bug 1571516, also remove the
additional failure-mode checks added in bug 1852801, reverting to a
crash (which can be unified with 1571516 when it shows up).
This has, as fallout, a minor functional change to the occlusion
tracker: the subfeature prefs mentioned above are now checked at
message-receipt time, rather than at construction time.
Differential Revision: https://phabricator.services.mozilla.com/D220639
2024-09-17 19:16:46 +00:00
|
|
|
WinEventWindow::Ensure();
|
2023-09-13 17:38:55 +00:00
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-01-06 04:31:03 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-01-12 00:12:18 +00:00
|
|
|
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) {
|
2021-10-06 07:21:59 +00:00
|
|
|
mNumVisibleRootWindows++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-17 19:16:46 +00:00
|
|
|
void WinWindowOcclusionTracker::OnSessionChange(WPARAM aStatusCode) {
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
Bug 1915665 - [1/2] Streamline WinEventHub r=win-reviewers,gfx-reviewers,handyman,bradwerth
`WinEventHub` was largely taken from Chromium, where its dynamic
connection/disconnection features are presumably more used. We don't
have any need for that, and anyway if we ever do it'll be easy to add
it alongside the static functionality or even migrate the latter over.
Hardwire the relevant message-processing and invocation directly into
the hidden window's WNDPROC, rather than providing a registration
system. Following the discoveries made in bug 1571516, also remove the
additional failure-mode checks added in bug 1852801, reverting to a
crash (which can be unified with 1571516 when it shows up).
This has, as fallout, a minor functional change to the occlusion
tracker: the subfeature prefs mentioned above are now checked at
message-receipt time, rather than at construction time.
Differential Revision: https://phabricator.services.mozilla.com/D220639
2024-09-17 19:16:46 +00:00
|
|
|
if (!StaticPrefs::
|
|
|
|
widget_windows_window_occlusion_tracking_session_lock_enabled()) {
|
|
|
|
return;
|
|
|
|
}
|
2024-09-12 07:59:44 +00:00
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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());
|
Bug 1915665 - [1/2] Streamline WinEventHub r=win-reviewers,gfx-reviewers,handyman,bradwerth
`WinEventHub` was largely taken from Chromium, where its dynamic
connection/disconnection features are presumably more used. We don't
have any need for that, and anyway if we ever do it'll be easy to add
it alongside the static functionality or even migrate the latter over.
Hardwire the relevant message-processing and invocation directly into
the hidden window's WNDPROC, rather than providing a registration
system. Following the discoveries made in bug 1571516, also remove the
additional failure-mode checks added in bug 1852801, reverting to a
crash (which can be unified with 1571516 when it shows up).
This has, as fallout, a minor functional change to the occlusion
tracker: the subfeature prefs mentioned above are now checked at
message-receipt time, rather than at construction time.
Differential Revision: https://phabricator.services.mozilla.com/D220639
2024-09-17 19:16:46 +00:00
|
|
|
if (!StaticPrefs::
|
|
|
|
widget_windows_window_occlusion_tracking_display_state_enabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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.
|
2022-01-12 00:12:18 +00:00
|
|
|
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)
|
2021-10-06 07:21:59 +00:00
|
|
|
? OcclusionState::HIDDEN
|
|
|
|
: OcclusionState::OCCLUDED;
|
2022-01-12 00:12:18 +00:00
|
|
|
baseWidget->NotifyOcclusionState(state);
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
void WinWindowOcclusionTracker::TriggerCalculation() {
|
|
|
|
RefPtr<Runnable> runnable =
|
|
|
|
WrapRunnable(RefPtr<WindowOcclusionCalculator>(
|
|
|
|
WindowOcclusionCalculator::GetInstance()),
|
|
|
|
&WindowOcclusionCalculator::HandleTriggerCalculation);
|
|
|
|
mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
|
|
|
|
}
|
|
|
|
|
2021-10-26 02:35:25 +00:00
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
// static
|
|
|
|
StaticRefPtr<WinWindowOcclusionTracker::WindowOcclusionCalculator>
|
|
|
|
WinWindowOcclusionTracker::WindowOcclusionCalculator::sCalculator;
|
|
|
|
|
|
|
|
WinWindowOcclusionTracker::WindowOcclusionCalculator::
|
2023-01-27 23:19:08 +00:00
|
|
|
WindowOcclusionCalculator()
|
|
|
|
: mMonitor(WinWindowOcclusionTracker::Get()->mMonitor) {
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
LOG(LogLevel::Info, "WindowOcclusionCalculator()");
|
|
|
|
|
2021-11-18 14:19:59 +00:00
|
|
|
mSerializedTaskDispatcher =
|
|
|
|
WinWindowOcclusionTracker::Get()->GetSerializedTaskDispatcher();
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
void WinWindowOcclusionTracker::WindowOcclusionCalculator::Initialize() {
|
|
|
|
MOZ_ASSERT(IsInWinWindowOcclusionThread());
|
|
|
|
MOZ_ASSERT(!mVirtualDesktopManager);
|
|
|
|
CALC_LOG(LogLevel::Info, "Initialize()");
|
|
|
|
|
|
|
|
RefPtr<IVirtualDesktopManager> desktopManager;
|
|
|
|
HRESULT hr = ::CoCreateInstance(
|
|
|
|
CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
|
|
|
|
__uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mVirtualDesktopManager = desktopManager;
|
|
|
|
}
|
|
|
|
|
2023-01-27 23:19:08 +00:00
|
|
|
void WinWindowOcclusionTracker::WindowOcclusionCalculator::Shutdown() {
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
MOZ_ASSERT(IsInWinWindowOcclusionThread());
|
|
|
|
CALC_LOG(LogLevel::Info, "Shutdown()");
|
|
|
|
|
|
|
|
UnregisterEventHooks();
|
|
|
|
if (mOcclusionUpdateRunnable) {
|
|
|
|
mOcclusionUpdateRunnable->Cancel();
|
|
|
|
mOcclusionUpdateRunnable = nullptr;
|
|
|
|
}
|
2022-02-15 14:33:06 +00:00
|
|
|
mVirtualDesktopManager = nullptr;
|
2023-01-27 23:19:08 +00:00
|
|
|
|
|
|
|
mMonitor.NotifyAll();
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
void WinWindowOcclusionTracker::WindowOcclusionCalculator::
|
|
|
|
HandleTriggerCalculation() {
|
|
|
|
MOZ_ASSERT(IsInWinWindowOcclusionThread());
|
|
|
|
CALC_LOG(LogLevel::Info, "HandleTriggerCalculation()");
|
|
|
|
|
|
|
|
MaybeRegisterEventHooks();
|
|
|
|
ScheduleOcclusionCalculationIfNeeded();
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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);
|
2021-11-17 00:59:38 +00:00
|
|
|
int screenWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
|
|
int screenHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
|
|
LayoutDeviceIntRegion screenRegion =
|
|
|
|
LayoutDeviceIntRect(screenLeft, screenTop, screenWidth, screenHeight);
|
2021-10-06 07:21:59 +00:00
|
|
|
mNumRootWindowsWithUnknownOcclusionState = 0;
|
|
|
|
|
2021-11-17 00:59:38 +00:00
|
|
|
CALC_LOG(LogLevel::Debug,
|
|
|
|
"ComputeNativeWindowOcclusionStatus() screen(%d, %d, %d, %d)",
|
|
|
|
screenLeft, screenTop, screenWidth, screenHeight);
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
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>(¤tPidsWithVisibleWindows));
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-06 04:31:03 +00:00
|
|
|
std::unordered_map<HWND, OcclusionState>* map =
|
|
|
|
&mRootWindowHwndsOcclusionState;
|
|
|
|
bool showAllWindows = mShowingThumbnails;
|
|
|
|
RefPtr<Runnable> runnable = NS_NewRunnableFunction(
|
|
|
|
"CallUpdateOcclusionState", [map, showAllWindows]() {
|
|
|
|
WinWindowOcclusionTracker::CallUpdateOcclusionState(map,
|
|
|
|
showAllWindows);
|
|
|
|
});
|
2021-10-06 07:21:59 +00:00
|
|
|
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()");
|
|
|
|
|
2022-07-21 15:09:06 +00:00
|
|
|
// Detects native window lost mouse capture
|
|
|
|
RegisterGlobalEventHook(EVENT_SYSTEM_CAPTUREEND, EVENT_SYSTEM_CAPTUREEND);
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2021-11-17 00:59:38 +00:00
|
|
|
CALC_LOG(LogLevel::Debug,
|
|
|
|
"ProcessComputeNativeWindowOcclusionStatusCallback() windowRect(%d, "
|
|
|
|
"%d, %d, %d) IsOccluding %d",
|
|
|
|
windowRect.x, windowRect.y, windowRect.width, windowRect.height,
|
|
|
|
windowIsOccluding);
|
|
|
|
|
2021-10-06 07:21:59 +00:00
|
|
|
// 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,
|
Bug 1766561 - Add missing l modifier for long formatting. r=Jamie,media-playback-reviewers,gfx-reviewers,nika,necko-reviewers,mhowell,rkraesig,gerald,application-update-reviewers,bytesized,alwu,kershaw
Differential Revision: https://phabricator.services.mozilla.com/D144917
2022-05-03 20:49:08 +00:00
|
|
|
"WindowOcclusionCalculator::ProcessEventHookCallback() aEvent 0x%lx",
|
2021-11-29 00:12:20 +00:00
|
|
|
aEvent);
|
2021-10-06 07:21:59 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2022-01-06 04:31:03 +00:00
|
|
|
std::unordered_map<HWND, OcclusionState>* map =
|
|
|
|
&mRootWindowHwndsOcclusionState;
|
|
|
|
bool showAllWindows = mShowingThumbnails;
|
|
|
|
RefPtr<Runnable> runnable = NS_NewRunnableFunction(
|
|
|
|
"CallUpdateOcclusionState", [map, showAllWindows]() {
|
|
|
|
WinWindowOcclusionTracker::CallUpdateOcclusionState(
|
|
|
|
map, showAllWindows);
|
|
|
|
});
|
2021-10-06 07:21:59 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-10-06 07:21:59 +00:00
|
|
|
|
2022-02-15 14:33:06 +00:00
|
|
|
return Some(false);
|
2021-10-06 07:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#undef LOG
|
|
|
|
#undef CALC_LOG
|
|
|
|
|
|
|
|
} // namespace mozilla::widget
|