| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/memory/raw_ptr.h" |
| #include "ui/aura/native_window_occlusion_tracker_win.h" |
| |
| #include <winuser.h> |
| |
| #include "base/at_exit.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/run_loop.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/win/scoped_gdi_object.h" |
| #include "base/win/windows_version.h" |
| #include "mojo/core/embedder/embedder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/test/aura_test_base.h" |
| #include "ui/aura/test/test_focus_client.h" |
| #include "ui/aura/test/test_screen.h" |
| #include "ui/aura/test/test_window_delegate.h" |
| #include "ui/aura/test/test_window_parenting_client.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/aura/window_occlusion_tracker.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/aura/window_tree_host_platform.h" |
| #include "ui/base/test/ui_controls.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/display.h" |
| #include "ui/display/win/dpi.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/win/singleton_hwnd.h" |
| #include "ui/gfx/win/window_impl.h" |
| #include "ui/gl/test/gl_surface_test_support.h" |
| |
| namespace aura { |
| |
| // This class is used to verify expectations about occlusion state changes by |
| // adding instances of it as an observer of aura:Windows the tests create and |
| // checking that they get the expected call(s) to OnOcclusionStateChanged. |
| // The tests verify that the current state, when idle, is the expected state, |
| // because the state can be VISIBLE before it reaches the expected state. |
| class MockWindowTreeHostObserver : public WindowTreeHostObserver { |
| public: |
| explicit MockWindowTreeHostObserver(base::OnceClosure quit_closure) |
| : quit_closure_(std::move(quit_closure)) {} |
| |
| MockWindowTreeHostObserver(const MockWindowTreeHostObserver&) = delete; |
| MockWindowTreeHostObserver& operator=(const MockWindowTreeHostObserver&) = |
| delete; |
| |
| ~MockWindowTreeHostObserver() override { EXPECT_FALSE(is_expecting_call()); } |
| |
| // WindowTreeHostObserver: |
| void OnOcclusionStateChanged(WindowTreeHost* host, |
| Window::OcclusionState new_state, |
| const SkRegion& occluded_region) override { |
| // Should only get notified when the occlusion state changes. |
| EXPECT_NE(new_state, cur_state_); |
| cur_state_ = new_state; |
| if (expectation_ != Window::OcclusionState::UNKNOWN && |
| cur_state_ == expectation_) { |
| EXPECT_FALSE(quit_closure_.is_null()); |
| std::move(quit_closure_).Run(); |
| } |
| } |
| |
| void set_quit_closure(base::OnceClosure quit_closure) { |
| quit_closure_ = std::move(quit_closure); |
| } |
| |
| void set_expectation(Window::OcclusionState expectation) { |
| expectation_ = expectation; |
| } |
| |
| bool is_expecting_call() const { return expectation_ != cur_state_; } |
| |
| private: |
| Window::OcclusionState expectation_ = Window::OcclusionState::UNKNOWN; |
| Window::OcclusionState cur_state_ = Window::OcclusionState::UNKNOWN; |
| base::OnceClosure quit_closure_; |
| }; |
| |
| class MockWindowObserver : public WindowObserver { |
| public: |
| explicit MockWindowObserver(Window* window) : window_(window) { |
| window_->AddObserver(this); |
| } |
| |
| ~MockWindowObserver() override { |
| if (window_) |
| window_->RemoveObserver(this); |
| } |
| |
| void set_quit_closure(base::OnceClosure quit_closure) { |
| quit_closure_ = std::move(quit_closure); |
| } |
| |
| void set_expectation(Window::OcclusionState expectation) { |
| expectation_ = expectation; |
| } |
| |
| // WindowObserver: |
| void OnWindowOcclusionChanged(Window* window) override { |
| if (expectation_ == window->GetOcclusionState()) { |
| ASSERT_FALSE(quit_closure_.is_null()); |
| std::move(quit_closure_).Run(); |
| } |
| } |
| |
| void OnWindowDestroyed(Window* window) override { |
| window_->RemoveObserver(this); |
| window_ = nullptr; |
| } |
| |
| private: |
| raw_ptr<Window> window_; |
| Window::OcclusionState expectation_ = Window::OcclusionState::UNKNOWN; |
| base::OnceClosure quit_closure_; |
| }; |
| |
| // Test wrapper around native window HWND. |
| class TestNativeWindow : public gfx::WindowImpl { |
| public: |
| TestNativeWindow() {} |
| |
| TestNativeWindow(const TestNativeWindow&) = delete; |
| TestNativeWindow& operator=(const TestNativeWindow&) = delete; |
| |
| ~TestNativeWindow() override; |
| |
| private: |
| // Overridden from gfx::WindowImpl: |
| BOOL ProcessWindowMessage(HWND window, |
| UINT message, |
| WPARAM w_param, |
| LPARAM l_param, |
| LRESULT& result, |
| DWORD msg_map_id) override { |
| return FALSE; // Results in DefWindowProc(). |
| } |
| }; |
| |
| TestNativeWindow::~TestNativeWindow() { |
| if (hwnd()) |
| DestroyWindow(hwnd()); |
| } |
| |
| class NativeWindowOcclusionTrackerTest : public test::AuraTestBase { |
| public: |
| NativeWindowOcclusionTrackerTest() { |
| // These interactive_ui_tests are not based on browser tests which would |
| // normally handle initializing mojo. We can safely initialize mojo at the |
| // start of the test here since a new process is launched for each test. |
| mojo::core::Init(); |
| } |
| |
| NativeWindowOcclusionTrackerTest(const NativeWindowOcclusionTrackerTest&) = |
| delete; |
| NativeWindowOcclusionTrackerTest& operator=( |
| const NativeWindowOcclusionTrackerTest&) = delete; |
| |
| void SetUp() override { |
| if (gl::GetGLImplementation() == gl::kGLImplementationNone) |
| gl::GLSurfaceTestSupport::InitializeOneOff(); |
| |
| scoped_feature_list_.InitWithFeatures( |
| {features::kCalculateNativeWinOcclusion, |
| features::kApplyNativeOccludedRegionToWindowTracker}, |
| {}); |
| |
| AuraTestBase::SetUp(); |
| } |
| |
| void SetNativeWindowBounds(HWND hwnd, const gfx::Rect& bounds) { |
| RECT wr = bounds.ToRECT(); |
| AdjustWindowRectEx(&wr, GetWindowLong(hwnd, GWL_STYLE), FALSE, |
| GetWindowLong(hwnd, 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. |
| gfx::Rect window_bounds(wr); |
| if (bounds.x() >= 0) |
| window_bounds.set_x(std::max(0, window_bounds.x())); |
| if (bounds.y() >= 0) |
| window_bounds.set_y(std::max(0, window_bounds.y())); |
| SetWindowPos(hwnd, HWND_TOP, window_bounds.x(), window_bounds.y(), |
| window_bounds.width(), window_bounds.height(), |
| SWP_NOREPOSITION); |
| EXPECT_TRUE(UpdateWindow(hwnd)); |
| } |
| |
| HWND CreateNativeWindowWithBounds(const gfx::Rect& bounds) { |
| std::unique_ptr<TestNativeWindow> native_win = |
| std::make_unique<TestNativeWindow>(); |
| native_win->set_window_style(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN); |
| native_win->Init(nullptr, bounds); |
| HWND hwnd = native_win->hwnd(); |
| SetNativeWindowBounds(hwnd, bounds); |
| base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0)); |
| if (GetWindowRgn(hwnd, region.get()) == 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 bounding_rect; |
| GetWindowRect(hwnd, &bounding_rect); |
| base::win::ScopedRegion rectangular_region( |
| CreateRectRgnIndirect(&bounding_rect)); |
| SetWindowRgn(hwnd, rectangular_region.get(), TRUE); |
| } |
| ShowWindow(hwnd, SW_SHOWNORMAL); |
| EXPECT_TRUE(UpdateWindow(hwnd)); |
| native_wins_.push_back(std::move(native_win)); |
| return hwnd; |
| } |
| |
| Window* CreateTrackedAuraWindowWithBounds( |
| MockWindowTreeHostObserver* observer, |
| const gfx::Rect& bounds) { |
| host()->Show(); |
| host()->SetBoundsInPixels(bounds); |
| if (observer) |
| host()->AddObserver(observer); |
| |
| Window* window = CreateNormalWindow(1, host()->window(), nullptr); |
| window->SetBounds(gfx::Rect(bounds.size())); |
| |
| Env::GetInstance()->GetWindowOcclusionTracker()->Track(window); |
| return window; |
| } |
| |
| int GetNumVisibleRootWindows() { |
| return NativeWindowOcclusionTrackerWin::GetOrCreateInstance() |
| ->num_visible_root_windows_; |
| } |
| |
| void MakeFullscreen(HWND hwnd) { |
| DWORD style = GetWindowLong(hwnd, GWL_STYLE); |
| DWORD ex_style = GetWindowLong(hwnd, GWL_STYLE); |
| SetWindowLong(hwnd, GWL_STYLE, style & ~(WS_CAPTION | WS_THICKFRAME)); |
| SetWindowLong(hwnd, GWL_EXSTYLE, |
| ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | |
| WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); |
| MONITORINFO monitor_info; |
| monitor_info.cbSize = sizeof(monitor_info); |
| GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), |
| &monitor_info); |
| gfx::Rect window_rect(monitor_info.rcMonitor); |
| SetWindowPos(hwnd, nullptr, window_rect.x(), window_rect.y(), |
| window_rect.width(), window_rect.height(), |
| SWP_FRAMECHANGED | SWP_ASYNCWINDOWPOS); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| std::vector<std::unique_ptr<TestNativeWindow>> native_wins_; |
| }; |
| |
| // Simple test completely covering an aura window with a native window. |
| TEST_F(NativeWindowOcclusionTrackerTest, SimpleOcclusion) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100)); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Simple test partially covering an aura window with a native window. |
| TEST_F(NativeWindowOcclusionTrackerTest, PartialOcclusion) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| CreateNativeWindowWithBounds(gfx::Rect(0, 0, 50, 50)); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Simple test that a partly off screen aura window, with the on screen part |
| // occluded by a native window, is considered occluded. |
| TEST_F(NativeWindowOcclusionTrackerTest, OffscreenOcclusion) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| |
| // Move the tracked window 50 pixels offscreen to the left. |
| int screen_left = GetSystemMetrics(SM_XVIRTUALSCREEN); |
| SetWindowPos(host()->GetAcceleratedWidget(), HWND_TOP, screen_left - 50, 0, |
| 100, 100, SWP_NOZORDER | SWP_NOSIZE); |
| |
| // Create a native window that covers the onscreen part of the tracked window. |
| CreateNativeWindowWithBounds(gfx::Rect(screen_left, 0, 50, 100)); |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Simple test with an aura window and native window that do not overlap. |
| TEST_F(NativeWindowOcclusionTrackerTest, SimpleVisible) { |
| base::RunLoop run_loop; |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100)); |
| |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Simple test with a minimized aura window and native window. |
| TEST_F(NativeWindowOcclusionTrackerTest, SimpleHidden) { |
| base::RunLoop run_loop; |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100)); |
| // Iconify the tracked aura window and check that its occlusion state |
| // is HIDDEN. |
| CloseWindow(host()->GetAcceleratedWidget()); |
| observer.set_expectation(Window::OcclusionState::HIDDEN); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Test that minimizing and restoring an app window results in the occlusion |
| // tracker re-registering for win events and detecting that a native window |
| // occludes the app window. |
| TEST_F(NativeWindowOcclusionTrackerTest, OcclusionAfterVisibilityToggle) { |
| base::RunLoop run_loop; |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| run_loop.Run(); |
| |
| base::RunLoop run_loop2; |
| observer.set_expectation(Window::OcclusionState::HIDDEN); |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| // host()->window()->Hide() is needed to generate OnWindowVisibilityChanged |
| // notifications. |
| host()->window()->Hide(); |
| // This makes the window iconic. |
| ::CloseWindow(host()->GetAcceleratedWidget()); |
| run_loop2.Run(); |
| // 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 app windows. |
| |
| do { |
| // Need to pump events in order for UpdateOcclusionState to get called, and |
| // update the number of non hidden root windows. When that number is 0, |
| // occlusion has been calculated with no visible root windows. |
| base::RunLoop().RunUntilIdle(); |
| } while (GetNumVisibleRootWindows() != 0); |
| |
| base::RunLoop run_loop3; |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| observer.set_quit_closure(run_loop3.QuitClosure()); |
| host()->window()->Show(); |
| // This opens the window made iconic above. |
| OpenIcon(host()->GetAcceleratedWidget()); |
| run_loop3.Run(); |
| |
| // Open a native window that occludes the visible app window. |
| base::RunLoop run_loop4; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop4.QuitClosure()); |
| CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100)); |
| run_loop4.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Test that locking the screen causes visible windows to become occluded. |
| TEST_F(NativeWindowOcclusionTrackerTest, LockScreenVisibleOcclusion) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| base::RunLoop run_loop2; |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| // 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 current_session_id = 0; |
| ProcessIdToSessionId(::GetCurrentProcessId(), ¤t_session_id); |
| PostMessage(gfx::SingletonHwnd::GetInstance()->hwnd(), WM_WTSSESSION_CHANGE, |
| WTS_SESSION_LOCK, current_session_id); |
| run_loop2.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Test that locking the screen leaves hidden windows as hidden. |
| TEST_F(NativeWindowOcclusionTrackerTest, LockScreenHiddenOcclusion) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| // Iconify the tracked aura window and check that its occlusion state |
| // is HIDDEN. |
| CloseWindow(host()->GetAcceleratedWidget()); |
| observer.set_expectation(Window::OcclusionState::HIDDEN); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| // Observer only gets notified on occlusion state changes, so force the |
| // state to VISIBLE so that setting the state to hidden will trigger |
| // a notification. |
| host()->SetNativeWindowOcclusionState(Window::OcclusionState::VISIBLE, {}); |
| |
| observer.set_expectation(Window::OcclusionState::HIDDEN); |
| base::RunLoop run_loop2; |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| // 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 current_session_id = 0; |
| ProcessIdToSessionId(::GetCurrentProcessId(), ¤t_session_id); |
| PostMessage(gfx::SingletonHwnd::GetInstance()->hwnd(), WM_WTSSESSION_CHANGE, |
| WTS_SESSION_LOCK, current_session_id); |
| run_loop2.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Test that locking the screen from a different session doesn't mark window |
| // as occluded. |
| TEST_F(NativeWindowOcclusionTrackerTest, LockScreenDifferentSession) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| // Observer only gets notified on occlusion state changes, so force the |
| // state to OCCLUDED so that setting the state to VISIBLE will trigger |
| // a notification. |
| host()->SetNativeWindowOcclusionState(Window::OcclusionState::OCCLUDED, {}); |
| |
| // Generate a session change lock screen with a session id that's not |
| // |current_session_id|. |
| DWORD current_session_id = 0; |
| ProcessIdToSessionId(::GetCurrentProcessId(), ¤t_session_id); |
| PostMessage(gfx::SingletonHwnd::GetInstance()->hwnd(), WM_WTSSESSION_CHANGE, |
| WTS_SESSION_LOCK, current_session_id + 1); |
| |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| base::RunLoop run_loop2; |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| // Create a native window to trigger occlusion calculation. |
| CreateNativeWindowWithBounds(gfx::Rect(0, 0, 50, 50)); |
| run_loop2.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Test that display off & on power state notification causes visible windows to |
| // become occluded, then visible. |
| TEST_F(NativeWindowOcclusionTrackerTest, DisplayOnOffHandling) { |
| base::RunLoop run_loop; |
| |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| NativeWindowOcclusionTrackerWin* occlusion_tracker = |
| NativeWindowOcclusionTrackerWin::GetOrCreateInstance(); |
| |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| base::RunLoop run_loop2; |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| |
| // Turning display off and on isn't feasible, so send a notification. |
| occlusion_tracker->OnDisplayStateChanged(/*display_on=*/false); |
| run_loop2.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| base::RunLoop run_loop3; |
| observer.set_quit_closure(run_loop3.QuitClosure()); |
| occlusion_tracker->OnDisplayStateChanged(/*display_on=*/true); |
| run_loop3.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Verifies that a window is not occluded if the only window occluding it is |
| // being moved/dragged. |
| // |
| // TODO(crbug.com/1266124): Flaky on Windows. |
| TEST_F(NativeWindowOcclusionTrackerTest, |
| DISABLED_MovingWindowNotConsideredInCalculations) { |
| // Needed as this test triggers a native nested message loop. |
| base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop |
| allow_nesting; |
| |
| // Create the initial window. |
| base::RunLoop run_loop; |
| MockWindowTreeHostObserver observer(run_loop.QuitClosure()); |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(40, 40, 100, 100)); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| run_loop.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| // Creates a new window that obscures the initial window. |
| CreateNativeWindowWithBounds(gfx::Rect(0, 0, 200, 200)); |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| base::RunLoop run_loop2; |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| run_loop2.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| // Start a window move loop. As windows being moved/dragged are not considered |
| // during occlusion calculation, the initial window should become visible. |
| base::RunLoop run_loop3(base::RunLoop::Type::kNestableTasksAllowed); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| observer.set_quit_closure(base::BindLambdaForTesting([&] { |
| // Release the mouse, which should make the initial window occluded. |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop3.QuitClosure()); |
| ASSERT_TRUE( |
| ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::UP)); |
| })); |
| ASSERT_TRUE(ui_controls::SendMouseMove(40, 8)); |
| ASSERT_TRUE( |
| ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::DOWN)); |
| run_loop3.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| |
| host()->RemoveObserver(&observer); |
| } |
| |
| // Test that a maximized aura window that is covered by a fullscreen window |
| // is marked as occluded. |
| TEST_F(NativeWindowOcclusionTrackerTest, MaximizedOccludedByFullscreenWindow) { |
| // Win7 has non rectangular windows and odd padding; this breaks fullscreen |
| // window occlusion of maximized windows, which makes this test fail on Win7. |
| // Win7 support is going away soon and shouldn't get in the way of this test |
| // coverage. |
| if (base::win::GetVersion() <= base::win::Version::WIN7) |
| return; |
| |
| // Create an aura window that is maximized. |
| base::RunLoop run_loop1; |
| MockWindowTreeHostObserver observer(run_loop1.QuitClosure()); |
| HWND hwnd_aura_window_maximized = |
| CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100)) |
| ->GetHost() |
| ->GetAcceleratedWidget(); |
| ShowWindow(hwnd_aura_window_maximized, SW_SHOWMAXIMIZED); |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| run_loop1.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| // Create a fullscreen native window that occludes the aura window. |
| base::RunLoop run_loop2; |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| HWND hwnd_native_window = |
| CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100)); |
| MakeFullscreen(hwnd_native_window); |
| run_loop2.Run(); |
| EXPECT_FALSE(observer.is_expecting_call()); |
| host()->RemoveObserver(&observer); |
| } |
| |
| TEST_F(NativeWindowOcclusionTrackerTest, OccludedRegionSimple) { |
| Window* tracked_aura_window = |
| CreateTrackedAuraWindowWithBounds(nullptr, gfx::Rect(20, 20, 200, 200)); |
| tracked_aura_window->SetBounds(gfx::Rect(0, 0, 60, 60)); |
| |
| MockWindowObserver observer(tracked_aura_window); |
| base::RunLoop run_loop; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop.QuitClosure()); |
| HWND obscuring_hwnd = |
| CreateNativeWindowWithBounds(gfx::Rect(20, 20, 110, 110)); |
| run_loop.Run(); |
| EXPECT_EQ(Window::OcclusionState::OCCLUDED, |
| tracked_aura_window->GetOcclusionState()); |
| |
| base::RunLoop run_loop2; |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| tracked_aura_window->SetBounds(gfx::Rect(160, 160, 20, 20)); |
| run_loop2.Run(); |
| EXPECT_EQ(Window::OcclusionState::VISIBLE, |
| tracked_aura_window->GetOcclusionState()); |
| |
| base::RunLoop run_loop3; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop3.QuitClosure()); |
| SetNativeWindowBounds(obscuring_hwnd, gfx::Rect(140, 140, 110, 110)); |
| run_loop3.Run(); |
| EXPECT_EQ(Window::OcclusionState::OCCLUDED, |
| tracked_aura_window->GetOcclusionState()); |
| } |
| |
| TEST_F(NativeWindowOcclusionTrackerTest, OccludedRegionComplex) { |
| Window* tracked_aura_window = |
| CreateTrackedAuraWindowWithBounds(nullptr, gfx::Rect(20, 20, 200, 200)); |
| tracked_aura_window->SetBounds(gfx::Rect(0, 0, 60, 60)); |
| |
| MockWindowObserver observer(tracked_aura_window); |
| base::RunLoop run_loop; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop.QuitClosure()); |
| CreateNativeWindowWithBounds(gfx::Rect(20, 20, 110, 110)); |
| run_loop.Run(); |
| EXPECT_EQ(Window::OcclusionState::OCCLUDED, |
| tracked_aura_window->GetOcclusionState()); |
| |
| base::RunLoop run_loop2; |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| tracked_aura_window->SetBounds(gfx::Rect(160, 160, 20, 20)); |
| run_loop2.Run(); |
| EXPECT_EQ(Window::OcclusionState::VISIBLE, |
| tracked_aura_window->GetOcclusionState()); |
| |
| base::RunLoop run_loop3; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop3.QuitClosure()); |
| CreateNativeWindowWithBounds(gfx::Rect(140, 140, 110, 110)); |
| run_loop3.Run(); |
| EXPECT_EQ(Window::OcclusionState::OCCLUDED, |
| tracked_aura_window->GetOcclusionState()); |
| } |
| |
| class NativeWindowOcclusionTrackerTestWithDpi2 |
| : public NativeWindowOcclusionTrackerTest { |
| public: |
| // NativeWindowOcclusionTrackerTest: |
| void SetUp() override { |
| display::Display::SetForceDeviceScaleFactor(2.0); |
| NativeWindowOcclusionTrackerTest::SetUp(); |
| } |
| }; |
| |
| TEST_F(NativeWindowOcclusionTrackerTestWithDpi2, OccludedRegionSimple) { |
| Window* tracked_aura_window = |
| CreateTrackedAuraWindowWithBounds(nullptr, gfx::Rect(20, 20, 200, 200)); |
| tracked_aura_window->SetBounds(gfx::Rect(0, 0, 30, 30)); |
| |
| MockWindowObserver observer(tracked_aura_window); |
| base::RunLoop run_loop; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop.QuitClosure()); |
| HWND obscuring_hwnd = |
| CreateNativeWindowWithBounds(gfx::Rect(20, 20, 110, 110)); |
| run_loop.Run(); |
| EXPECT_EQ(Window::OcclusionState::OCCLUDED, |
| tracked_aura_window->GetOcclusionState()); |
| |
| base::RunLoop run_loop2; |
| observer.set_expectation(Window::OcclusionState::VISIBLE); |
| observer.set_quit_closure(run_loop2.QuitClosure()); |
| tracked_aura_window->SetBounds(gfx::Rect(80, 80, 20, 20)); |
| run_loop2.Run(); |
| EXPECT_EQ(Window::OcclusionState::VISIBLE, |
| tracked_aura_window->GetOcclusionState()); |
| |
| base::RunLoop run_loop3; |
| observer.set_expectation(Window::OcclusionState::OCCLUDED); |
| observer.set_quit_closure(run_loop3.QuitClosure()); |
| SetNativeWindowBounds(obscuring_hwnd, gfx::Rect(140, 140, 110, 110)); |
| run_loop3.Run(); |
| EXPECT_EQ(Window::OcclusionState::OCCLUDED, |
| tracked_aura_window->GetOcclusionState()); |
| } |
| |
| } // namespace aura |