diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp | |
parent | Initial commit. (diff) | |
download | firefox-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.cpp | 402 |
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(), ¤tSessionId); + ::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(), ¤tSessionId); + 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(), ¤tSessionId); + ::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()); +} |