blob: fd67384c880b5c71542b25db4cb59bc82c1254a6 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/headless/headless_mode_browsertest.h"
#include <windows.h>
#include <set>
#include "base/strings/stringprintf.h"
#include "chrome/browser/headless/headless_mode_browsertest_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/ui_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/compositor/layer_type.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/win/screen_win_headless.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/switches.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"
#include "ui/views/widget/widget.h"
namespace headless {
// A class to expose protected methods for testing purposes.
class DesktopWindowTreeHostWinWrapper : public views::DesktopWindowTreeHostWin {
public:
HWND GetHWND() const { return DesktopWindowTreeHostWin::GetHWND(); }
gfx::Rect GetWindowBoundsInScreen() const override {
return DesktopWindowTreeHostWin::GetWindowBoundsInScreen();
}
};
namespace test {
bool IsPlatformWindowVisible(views::Widget* widget) {
CHECK(widget);
gfx::NativeWindow native_window = widget->GetNativeWindow();
CHECK(native_window);
aura::WindowTreeHost* host = native_window->GetHost();
CHECK(host);
gfx::AcceleratedWidget accelerated_widget = host->GetAcceleratedWidget();
CHECK(::IsWindow(accelerated_widget));
return !!::IsWindowVisible(accelerated_widget);
}
gfx::Rect GetPlatformWindowExpectedBounds(views::Widget* widget) {
CHECK(widget);
gfx::NativeWindow native_window = widget->GetNativeWindow();
CHECK(native_window);
DesktopWindowTreeHostWinWrapper* host =
static_cast<DesktopWindowTreeHostWinWrapper*>(native_window->GetHost());
CHECK(host);
return host->GetWindowBoundsInScreen();
}
aura::Window* CreateAuraWindow(aura::Window* parent, const gfx::Rect& bounds) {
CHECK(parent);
aura::test::TestWindowDelegate* delegate =
aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
aura::Window* window = new aura::Window(delegate);
window->Init(ui::LayerType::LAYER_NOT_DRAWN);
window->SetBounds(bounds);
window->Show();
parent->AddChild(window);
return window;
}
int GetSystemFrameThickness() {
// Mimic ui::GetFrameThickness() for 1x.
const int resize_frame_thickness = ::GetSystemMetrics(SM_CXSIZEFRAME);
const int padding_thickness = ::GetSystemMetrics(SM_CXPADDEDBORDER);
return resize_frame_thickness + padding_thickness;
}
} // namespace test
namespace {
INSTANTIATE_TEST_SUITE_P(HeadlessModeBrowserTestWithStartWindowMode,
HeadlessModeBrowserTestWithStartWindowMode,
testing::Values(kStartWindowNormal,
kStartWindowMaximized,
kStartWindowFullscreen));
IN_PROC_BROWSER_TEST_P(HeadlessModeBrowserTestWithStartWindowMode,
BrowserDesktopWindowHidden) {
// On Windows, the Native Headless Chrome browser window exists and is
// visible, while the underlying platform window is hidden.
EXPECT_TRUE(browser()->window()->IsVisible());
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
EXPECT_FALSE(::IsWindowVisible(desktop_window_tree_host->GetHWND()));
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest,
ToggleFullscreenWindowVisibility) {
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
HWND desktop_window_hwnd = desktop_window_tree_host->GetHWND();
// Verify initial state.
ASSERT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
// Verify fullscreen state.
ui_test_utils::ToggleFullscreenModeAndWait(browser());
ASSERT_TRUE(browser()->window()->IsFullscreen());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
// Verify back to normal state.
ui_test_utils::ToggleFullscreenModeAndWait(browser());
ASSERT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest,
MinimizedRestoredWindowVisibility) {
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
HWND desktop_window_hwnd = desktop_window_tree_host->GetHWND();
// Verify initial state.
ASSERT_FALSE(browser()->window()->IsMinimized());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
// Verify minimized state.
browser()->window()->Minimize();
ASSERT_TRUE(browser()->window()->IsMinimized());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
// Verify restored state.
browser()->window()->Restore();
ASSERT_FALSE(browser()->window()->IsMinimized());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest,
MaximizedRestoredWindowVisibility) {
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
HWND desktop_window_hwnd = desktop_window_tree_host->GetHWND();
// Verify initial state.
ASSERT_FALSE(browser()->window()->IsMaximized());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
// Verify maximized state.
browser()->window()->Maximize();
ASSERT_TRUE(browser()->window()->IsMaximized());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
// Verify restored state.
browser()->window()->Restore();
ASSERT_FALSE(browser()->window()->IsMaximized());
EXPECT_TRUE(browser()->window()->IsVisible());
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
}
// display::win::ScreenWinHeadless tests -------------------------------------
class HeadlessModeBrowserTestWithScreenInfo : public HeadlessModeBrowserTest {
public:
HeadlessModeBrowserTestWithScreenInfo() = default;
~HeadlessModeBrowserTestWithScreenInfo() override = default;
virtual std::string GetScreenInfo() { return "{}"; }
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(::switches::kScreenInfo, GetScreenInfo());
}
display::win::ScreenWinHeadless* screen() const {
return static_cast<display::win::ScreenWinHeadless*>(
display::Screen::GetScreen());
}
};
#define HEADLESS_MODE_PROTOCOL_TEST_WITH_SCREEN_INFO(TEST_NAME, SCREEEN_INFO) \
class HeadlessModeBrowserTestWithScreenInfo_##TEST_NAME \
: public HeadlessModeBrowserTestWithScreenInfo { \
public: \
std::string GetScreenInfo() override { \
return SCREEEN_INFO; \
} \
}; \
\
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithScreenInfo_##TEST_NAME, \
TEST_NAME)
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithScreenInfo,
GetCursorScreenPoint) {
EXPECT_TRUE(screen()->GetCursorScreenPoint().IsOrigin());
static constexpr gfx::Point kPoint(123, 456);
screen()->SetCursorScreenPointForTesting(kPoint);
EXPECT_EQ(screen()->GetCursorScreenPoint(), kPoint);
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithScreenInfo,
GetWindowAtScreenPoint) {
// Try off screen point.
EXPECT_FALSE(screen()->GetWindowAtScreenPoint(gfx::Point(-100, -200)));
// Try window at screen center.
static constexpr gfx::Point kScreenCenter(800 / 2, 600 / 2);
aura::Window* window = screen()->GetWindowAtScreenPoint(kScreenCenter);
ASSERT_TRUE(window);
EXPECT_TRUE(window->GetBoundsInScreen().Contains(kScreenCenter));
// Try overlapping child window.
gfx::Rect child_bounds = window->bounds();
child_bounds.Inset(42);
aura::Window* child_window = test::CreateAuraWindow(window, child_bounds);
EXPECT_EQ(screen()->GetWindowAtScreenPoint(kScreenCenter), child_window);
// Try overlapping hidden child window.
child_window->Hide();
EXPECT_NE(screen()->GetWindowAtScreenPoint(kScreenCenter), child_window);
EXPECT_EQ(screen()->GetWindowAtScreenPoint(kScreenCenter), window);
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithScreenInfo,
GetWindowAtScreenPointOnSiblingWindows) {
static constexpr gfx::Point kScreenCenter(800 / 2, 600 / 2);
aura::Window* window = screen()->GetWindowAtScreenPoint(kScreenCenter);
gfx::Rect child_bounds = window->bounds();
child_bounds.Inset(42);
// Try overlapping child window.
aura::Window* child_window = test::CreateAuraWindow(window, child_bounds);
EXPECT_EQ(screen()->GetWindowAtScreenPoint(kScreenCenter), child_window);
// Try overlapping sibling window.
aura::Window* child_window2 = test::CreateAuraWindow(window, child_bounds);
EXPECT_EQ(screen()->GetWindowAtScreenPoint(kScreenCenter), child_window2);
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithScreenInfo,
GetWindowAtScreenPointOnChildWindowTree) {
static constexpr gfx::Point kScreenCenter(800 / 2, 600 / 2);
aura::Window* window = screen()->GetWindowAtScreenPoint(kScreenCenter);
gfx::Rect child_bounds = window->bounds();
child_bounds.Inset(42);
// Try overlapping child window.
aura::Window* child_window = test::CreateAuraWindow(window, child_bounds);
EXPECT_EQ(screen()->GetWindowAtScreenPoint(kScreenCenter), child_window);
// Try overlapping grandchild window.
aura::Window* child_window2 =
test::CreateAuraWindow(child_window, child_bounds);
EXPECT_EQ(screen()->GetWindowAtScreenPoint(kScreenCenter), child_window2);
}
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithScreenInfo,
GetLocalProcessWindowAtPoint) {
// Get window at screen center.
static constexpr gfx::Point kScreenCenter(800 / 2, 600 / 2);
aura::Window* window = screen()->GetWindowAtScreenPoint(kScreenCenter);
ASSERT_TRUE(window);
ASSERT_TRUE(window->GetBoundsInScreen().Contains(kScreenCenter));
std::set<gfx::NativeWindow> ignore;
// Try overlapping child window.
gfx::Rect child_bounds = window->bounds();
child_bounds.Inset(42);
aura::Window* child_window = test::CreateAuraWindow(window, child_bounds);
EXPECT_EQ(screen()->GetLocalProcessWindowAtPoint(kScreenCenter, ignore),
child_window->GetRootWindow());
// Try ignored overlapping child window.
ignore.insert(child_window);
EXPECT_EQ(screen()->GetLocalProcessWindowAtPoint(kScreenCenter, ignore),
window->GetRootWindow());
}
class HeadlessModeBrowserTest2ndScreen
: public HeadlessModeBrowserTestWithScreenInfo {
public:
HeadlessModeBrowserTest2ndScreen() = default;
~HeadlessModeBrowserTest2ndScreen() override = default;
std::string GetScreenInfo() override { return "{}{}"; }
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeBrowserTestWithScreenInfo::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(::switches::kWindowPosition, "800,0");
}
};
IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest2ndScreen,
GetWindowAt2ndScreenPoint) {
ASSERT_EQ(display::Screen::GetScreen()->GetNumDisplays(), 2);
// Try off 2nd screen point.
EXPECT_FALSE(screen()->GetWindowAtScreenPoint(gfx::Point(800 - 100, 0)));
// Try window at the 2nd screen center.
static constexpr gfx::Point kScreenCenter(800 + 800 / 2, 600 / 2);
aura::Window* window = screen()->GetWindowAtScreenPoint(kScreenCenter);
ASSERT_TRUE(window);
EXPECT_TRUE(window->GetBoundsInScreen().Contains(kScreenCenter));
}
HEADLESS_MODE_PROTOCOL_TEST_WITH_SCREEN_INFO(GetFrameThicknessFromScreenRect,
"{}{devicePixelRatio=2.0}") {
ASSERT_EQ(screen()->GetNumDisplays(), 2);
ASSERT_EQ(screen()->GetAllDisplays()[0].device_scale_factor(), 1.f);
ASSERT_EQ(screen()->GetAllDisplays()[1].device_scale_factor(), 2.f);
const int kSystemFrameThickness = test::GetSystemFrameThickness();
EXPECT_EQ(ui::GetFrameThicknessFromScreenRect(gfx::Rect(0, 0, 10, 20)),
kSystemFrameThickness);
EXPECT_EQ(ui::GetFrameThicknessFromScreenRect(gfx::Rect(800, 600, 10, 20)),
kSystemFrameThickness * 2);
}
HEADLESS_MODE_PROTOCOL_TEST_WITH_SCREEN_INFO(GetFrameThicknessFromWindow,
"{devicePixelRatio=2.0}") {
ASSERT_EQ(screen()->GetNumDisplays(), 1);
ASSERT_EQ(screen()->GetAllDisplays()[0].device_scale_factor(), 2.f);
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
HWND desktop_window_hwnd = desktop_window_tree_host->GetHWND();
const int kSystemFrameThickness = test::GetSystemFrameThickness();
EXPECT_EQ(ui::GetFrameThicknessFromWindow(desktop_window_hwnd,
MONITOR_DEFAULTTONEAREST),
kSystemFrameThickness * 2);
}
} // namespace
} // namespace headless