summaryrefslogtreecommitdiffstats
path: root/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp')
-rw-r--r--widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp402
1 files changed, 402 insertions, 0 deletions
diff --git a/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp b/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
new file mode 100644
index 0000000000..13cd95651c
--- /dev/null
+++ b/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
@@ -0,0 +1,402 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "MockWinWidget.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/widget/WinEventObserver.h"
+#include "mozilla/widget/WinWindowOcclusionTracker.h"
+#include "nsThreadUtils.h"
+#include "Units.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define PREF_DISPLAY_STATE \
+ "widget.windows.window_occlusion_tracking_display_state.enabled"
+#define PREF_SESSION_LOCK \
+ "widget.windows.window_occlusion_tracking_session_lock.enabled"
+
+class WinWindowOcclusionTrackerInteractiveTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Shut down WinWindowOcclusionTracker if it exists.
+ // This could happen when WinWindowOcclusionTracker was initialized by other
+ // gtest
+ if (WinWindowOcclusionTracker::Get()) {
+ WinWindowOcclusionTracker::ShutDown();
+ }
+ EXPECT_EQ(nullptr, WinWindowOcclusionTracker::Get());
+
+ WinWindowOcclusionTracker::Ensure();
+ EXPECT_NE(nullptr, WinWindowOcclusionTracker::Get());
+
+ WinWindowOcclusionTracker::Get()->EnsureDisplayStatusObserver();
+ WinWindowOcclusionTracker::Get()->EnsureSessionChangeObserver();
+ }
+
+ void TearDown() override {
+ WinWindowOcclusionTracker::ShutDown();
+ EXPECT_EQ(nullptr, WinWindowOcclusionTracker::Get());
+ }
+
+ void SetNativeWindowBounds(HWND aHWnd, const LayoutDeviceIntRect aBounds) {
+ RECT wr;
+ wr.left = aBounds.X();
+ wr.top = aBounds.Y();
+ wr.right = aBounds.XMost();
+ wr.bottom = aBounds.YMost();
+
+ ::AdjustWindowRectEx(&wr, ::GetWindowLong(aHWnd, GWL_STYLE), FALSE,
+ ::GetWindowLong(aHWnd, GWL_EXSTYLE));
+
+ // Make sure to keep the window onscreen, as AdjustWindowRectEx() may have
+ // moved part of it offscreen. But, if the original requested bounds are
+ // offscreen, don't adjust the position.
+ LayoutDeviceIntRect windowBounds(wr.left, wr.top, wr.right - wr.left,
+ wr.bottom - wr.top);
+
+ if (aBounds.X() >= 0) {
+ windowBounds.x = std::max(0, windowBounds.X());
+ }
+ if (aBounds.Y() >= 0) {
+ windowBounds.y = std::max(0, windowBounds.Y());
+ }
+ ::SetWindowPos(aHWnd, HWND_TOP, windowBounds.X(), windowBounds.Y(),
+ windowBounds.Width(), windowBounds.Height(),
+ SWP_NOREPOSITION);
+ EXPECT_TRUE(::UpdateWindow(aHWnd));
+ }
+
+ void CreateNativeWindowWithBounds(LayoutDeviceIntRect aBounds) {
+ mMockWinWidget = MockWinWidget::Create(
+ WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, /* aExStyle = */ 0, aBounds);
+ EXPECT_NE(nullptr, mMockWinWidget.get());
+ HWND hwnd = mMockWinWidget->GetWnd();
+ SetNativeWindowBounds(hwnd, aBounds);
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ EXPECT_NE(nullptr, region);
+ if (::GetWindowRgn(hwnd, region) == COMPLEXREGION) {
+ // On Windows 7, the newly created window has a complex region, which
+ // means it will be ignored during the occlusion calculation. So, force
+ // it to have a simple region so that we get test coverage on win 7.
+ RECT boundingRect;
+ EXPECT_TRUE(::GetWindowRect(hwnd, &boundingRect));
+ HRGN rectangularRegion = ::CreateRectRgnIndirect(&boundingRect);
+ EXPECT_NE(nullptr, rectangularRegion);
+ ::SetWindowRgn(hwnd, rectangularRegion, /* bRedraw = */ TRUE);
+ ::DeleteObject(rectangularRegion);
+ }
+ ::DeleteObject(region);
+
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ EXPECT_TRUE(UpdateWindow(hwnd));
+ }
+
+ RefPtr<MockWinWidget> CreateTrackedWindowWithBounds(
+ LayoutDeviceIntRect aBounds) {
+ RefPtr<MockWinWidget> window = MockWinWidget::Create(
+ WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, /* aExStyle */ 0, aBounds);
+ EXPECT_NE(nullptr, window.get());
+ HWND hwnd = window->GetWnd();
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ WinWindowOcclusionTracker::Get()->Enable(window, window->GetWnd());
+ return window;
+ }
+
+ int GetNumVisibleRootWindows() {
+ return WinWindowOcclusionTracker::Get()->mNumVisibleRootWindows;
+ }
+
+ void OnDisplayStateChanged(bool aDisplayOn) {
+ WinWindowOcclusionTracker::Get()->OnDisplayStateChanged(aDisplayOn);
+ }
+
+ RefPtr<MockWinWidget> mMockWinWidget;
+};
+
+// Simple test completely covering a tracked window with a native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test partially covering a tracked window with a native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, PartialOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 200, 200));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 50, 50));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test that a partly off screen tracked window, with the on screen part
+// occluded by a native window, is considered occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, OffscreenOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ // Move the tracked window 50 pixels offscreen to the left.
+ int screenLeft = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+ ::SetWindowPos(window->GetWnd(), HWND_TOP, screenLeft - 50, 0, 100, 100,
+ SWP_NOZORDER | SWP_NOSIZE);
+
+ // Create a native window that covers the onscreen part of the tracked window.
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(screenLeft, 0, 50, 100));
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test with a tracked window and native window that do not overlap.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleVisible) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(200, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test with a minimized tracked window and native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleHidden) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(200, 0, 100, 100));
+ // Iconify the tracked window and check that its occlusion state is HIDDEN.
+ ::CloseWindow(window->GetWnd());
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that minimizing and restoring a tracked window results in the occlusion
+// tracker re-registering for win events and detecting that a native window
+// occludes the tracked window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest,
+ OcclusionAfterVisibilityToggle) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ window, /* aVisible = */ false);
+
+ // This makes the window iconic.
+ ::CloseWindow(window->GetWnd());
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // HIDDEN state is set synchronously by OnWindowVsiblityChanged notification,
+ // before occlusion is calculated, so the above expectation will be met w/o an
+ // occlusion calculation.
+ // Loop until an occlusion calculation has run with no non-hidden windows.
+ while (GetNumVisibleRootWindows() != 0) {
+ // Need to pump events in order for UpdateOcclusionState to get called, and
+ // update the number of non hidden windows. When that number is 0,
+ // occlusion has been calculated with no visible tracked windows.
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ window, /* aVisible = */ true);
+ // This opens the window made iconic above.
+ ::OpenIcon(window->GetWnd());
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Open a native window that occludes the visible tracked window.
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen causes visible windows to become occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenVisibleOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
+ // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
+ // actually locking the screen isn't feasible.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ ::PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId);
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen leaves hidden windows as hidden.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenHiddenOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ // Iconify the tracked window and check that its occlusion state is HIDDEN.
+ ::CloseWindow(window->GetWnd());
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Force the state to VISIBLE.
+ window->NotifyOcclusionState(widget::OcclusionState::VISIBLE);
+
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+
+ // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
+ // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
+ // actually locking the screen isn't feasible.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId);
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen from a different session doesn't mark window
+// as occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenDifferentSession) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 200, 200));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Force the state to OCCLUDED.
+ window->NotifyOcclusionState(widget::OcclusionState::OCCLUDED);
+
+ // Generate a session change lock screen with a session id that's not
+ // currentSessionId.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ ::PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId + 1);
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ // Create a native window to trigger occlusion calculation.
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 50, 50));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that display off & on power state notification causes visible windows to
+// become occluded, then visible.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, DisplayOnOffHandling) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+
+ // Turning display off and on isn't feasible, so send a notification.
+ OnDisplayStateChanged(/* aDisplayOn */ false);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ OnDisplayStateChanged(/* aDisplayOn */ true);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}