Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/widget/windows/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 48 kB image not shown  

SSL WinWindowOcclusionTracker.cpp   Sprache: 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

95%


¤ Dauer der Verarbeitung: 0.52 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.