chromium/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc

// Copyright 2018 The Chromium Authors
// 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 "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);
    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(), &current_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(), &current_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(), &current_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/40801894): 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. TODO(crbug.com/40833493): Fix flakiness.
TEST_F(NativeWindowOcclusionTrackerTest,
       DISABLED_MaximizedOccludedByFullscreenWindow) {
  // 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