mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
ce8c2d0f42
The condition was added in https://phabricator.services.mozilla.com/D133327
IVirtualDesktopManager was added in b2222231e3
/
Differential Revision: https://phabricator.services.mozilla.com/D224057
1440 lines
48 KiB
C++
1440 lines
48 KiB
C++
/* -*- 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 "WinEventObserver.h"
|
|
#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:
|
|
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;
|
|
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 (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;
|
|
}
|
|
|
|
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()");
|
|
|
|
WinEventWindow::Ensure();
|
|
|
|
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) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!StaticPrefs::
|
|
widget_windows_window_occlusion_tracking_session_lock_enabled()) {
|
|
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());
|
|
if (!StaticPrefs::
|
|
widget_windows_window_occlusion_tracking_display_state_enabled()) {
|
|
return;
|
|
}
|
|
|
|
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()");
|
|
|
|
RefPtr<IVirtualDesktopManager> desktopManager;
|
|
HRESULT hr = ::CoCreateInstance(
|
|
CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
|
|
__uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
|
|
if (FAILED(hr)) {
|
|
return;
|
|
}
|
|
mVirtualDesktopManager = desktopManager;
|
|
}
|
|
|
|
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>(¤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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|