Skip to content

Commit

Permalink
[headless] Added expected window bounds tracking on Windows
Browse files Browse the repository at this point in the history
Windows clamps newly created platform window to the desktop work area. This results in unexpected behavior in headless mode in which host system display parameters should not be considered and headless Chrome window is supposed to be either default 800x600 or the size specified by --window-size switch. This is how old headless behaves on all platforms and new headless behaves on Linux, thanks to Ozone/headless.

This CL introduces expected window size tracking: the requested window size is remembered when the platform window is created and after that it is changing only when SetBounds()/SetSize() are called irrespective to the platform window size. Upper layers observe the expected window size.

Bug: 1416398
Change-Id: Ieb094f756bfdf25a8b7c57c3120951a34f9256ee
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4252005
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1107080}
  • Loading branch information
Peter Kvitek authored and Chromium LUCI CQ committed Feb 18, 2023
1 parent 91b2e66 commit fad2345
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 7 deletions.
3 changes: 3 additions & 0 deletions chrome/browser/headless/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ if (!is_android) {
"//components/devtools/simple_devtools_protocol_client",
"//components/headless/clipboard",
"//content/public/browser",
"//ui/display",
"//ui/gfx",
"//ui/views",
]
}

Expand Down
77 changes: 77 additions & 0 deletions chrome/browser/headless/headless_mode_browsertest_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@

#include "chrome/browser/headless/headless_mode_browsertest.h"

#include "base/strings/stringprintf.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display_switches.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"

namespace headless {
Expand Down Expand Up @@ -112,6 +117,78 @@ IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest,
EXPECT_FALSE(::IsWindowVisible(desktop_window_hwnd));
}

class HeadlessModeBrowserTestWithWindowSize : public HeadlessModeBrowserTest {
public:
static constexpr gfx::Size kWindowSize = {4096, 2160};

HeadlessModeBrowserTestWithWindowSize() = default;
~HeadlessModeBrowserTestWithWindowSize() override = default;

void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
::switches::kWindowSize,
base::StringPrintf("%u,%u", kWindowSize.width(), kWindowSize.height()));
}
};

IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithWindowSize, LargeWindowSize) {
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
HWND desktop_window_hwnd = desktop_window_tree_host->GetHWND();

// Expect the platform window size to be smaller than the requested window
// size due to Windows clamping the window dimensions to the monitor work
// area.
RECT platform_window_rect;
CHECK(::GetWindowRect(desktop_window_hwnd, &platform_window_rect));
EXPECT_LT(gfx::Rect(platform_window_rect).width(), kWindowSize.width());
EXPECT_LT(gfx::Rect(platform_window_rect).height(), kWindowSize.height());

// Expect the reported browser window size to be the same as the requested
// window size.
gfx::Rect bounds = browser()->window()->GetBounds();
EXPECT_EQ(bounds.size(), kWindowSize);
}

class HeadlessModeBrowserTestWithWindowSizeAndScale
: public HeadlessModeBrowserTest {
public:
static constexpr gfx::Size kWindowSize = {800, 600};

HeadlessModeBrowserTestWithWindowSizeAndScale() = default;
~HeadlessModeBrowserTestWithWindowSizeAndScale() override = default;

void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
::switches::kWindowSize,
base::StringPrintf("%u,%u", kWindowSize.width(), kWindowSize.height()));
command_line->AppendSwitchASCII(::switches::kForceDeviceScaleFactor, "1.5");
}
};

IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithWindowSizeAndScale,
WindowSizeWithScale) {
DesktopWindowTreeHostWinWrapper* desktop_window_tree_host =
static_cast<DesktopWindowTreeHostWinWrapper*>(
browser()->window()->GetNativeWindow()->GetHost());
HWND desktop_window_hwnd = desktop_window_tree_host->GetHWND();

// Expect the platform window size to be larger than the requested window size
// due to scaling.
RECT platform_window_rect;
CHECK(::GetWindowRect(desktop_window_hwnd, &platform_window_rect));
EXPECT_GT(gfx::Rect(platform_window_rect).width(), kWindowSize.width());
EXPECT_GT(gfx::Rect(platform_window_rect).height(), kWindowSize.height());

// Expect the reported browser window size to be the same as the requested
// window size.
gfx::Rect bounds = browser()->window()->GetBounds();
EXPECT_EQ(bounds.size(), kWindowSize);
}

} // namespace

} // namespace headless
117 changes: 110 additions & 7 deletions ui/views/win/hwnd_message_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
#include "ui/events/win/system_event_state_lookup.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/resize_utils.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/path_win.h"
Expand Down Expand Up @@ -307,6 +309,20 @@ constexpr auto kTouchDownContextResetTimeout = base::Milliseconds(500);
// same location as the cursor.
constexpr int kSynthesizedMouseMessagesTimeDifference = 500;

// This is used in headless mode where we have to manually scale window
// bounds because we cannot rely on the platform window size since it gets
// clamped to the monitor work area.
gfx::Rect ScaleWindowBoundsMaybe(HWND hwnd, const gfx::Rect& bounds) {
const float scale = display::win::ScreenWin::GetScaleFactorForHWND(hwnd);
if (scale > 1.0) {
gfx::RectF scaled_bounds(bounds);
scaled_bounds.Scale(scale);
return gfx::ToEnclosingRect(scaled_bounds);
}

return bounds;
}

} // namespace

// A scoping class that prevents a window from being able to redraw in response
Expand Down Expand Up @@ -446,12 +462,36 @@ void HWNDMessageHandler::Init(HWND parent,
initial_bounds_valid_ = !bounds.IsEmpty();

// Provide the headless mode window state container.
if (headless_mode)
if (headless_mode) {
headless_mode_window_ = absl::make_optional<HeadlessModeWindow>();
}

// Create the window.
WindowImpl::Init(parent, bounds);

// In headless mode remember the expected window bounds possibly adjusted
// according to the scale factor.
if (headless_mode) {
if (initial_bounds_valid_) {
headless_mode_window_->bounds = bounds;
} else {
// If initial window bounds were not provided, use the newly created
// platform window size or fall back to the default headless window size
// as the last resort.
RECT window_rect;
if (GetWindowRect(hwnd(), &window_rect)) {
headless_mode_window_->bounds = gfx::Rect(window_rect);
} else {
// Even if the window rectangle cannot be retrieved, there is still a
// chance that ScreenWin::GetScaleFactorForHWND() will be able to figure
// out the scale factor.
constexpr gfx::Rect kDefaultHeadlessBounds(800, 600);
headless_mode_window_->bounds =
ScaleWindowBoundsMaybe(hwnd(), kDefaultHeadlessBounds);
}
}
}

if (!called_enable_non_client_dpi_scaling_ && delegate_->HasFrame()) {
// Derived signature; not available in headers.
// This call gets Windows to scale the non-client area when
Expand Down Expand Up @@ -534,12 +574,48 @@ void HWNDMessageHandler::CloseNow() {
}

gfx::Rect HWNDMessageHandler::GetWindowBoundsInScreen() const {
// In headless mode return the expected window rectangle set in Init() and
// updated in SetBounds() and SetSize().
if (IsHeadless()) {
return headless_mode_window_->bounds;
}

RECT r;
GetWindowRect(hwnd(), &r);
return gfx::Rect(r);
}

gfx::Rect HWNDMessageHandler::GetClientAreaBoundsInScreen() const {
// In headless mode calculate the client rectangle using the difference
// between platform window and client rectangles.
if (IsHeadless()) {
gfx::Insets client_insets;
if (!GetClientAreaInsets(&client_insets, last_monitor_)) {
RECT window_rect;
if (!GetWindowRect(hwnd(), &window_rect)) {
return gfx::Rect();
}

RECT client_rect;
if (!GetClientRect(hwnd(), &client_rect)) {
return gfx::Rect(window_rect);
}

client_insets.set_left(client_rect.left - window_rect.left);
client_insets.set_right(window_rect.right - client_rect.right);
client_insets.set_top(client_rect.top - window_rect.top);
client_insets.set_bottom(window_rect.bottom - client_rect.bottom);
}

gfx::Rect bounds = headless_mode_window_->bounds;
bounds.Inset(client_insets);
if (bounds.IsEmpty()) {
return headless_mode_window_->bounds;
}

return bounds;
}

RECT r;
GetClientRect(hwnd(), &r);
POINT point = {r.left, r.top};
Expand All @@ -548,10 +624,11 @@ gfx::Rect HWNDMessageHandler::GetClientAreaBoundsInScreen() const {
}

gfx::Rect HWNDMessageHandler::GetRestoredBounds() const {
// Headless mode window never goes fullscreen, so just return an empty
// rectangle here.
if (IsHeadless())
return gfx::Rect();
// Headless mode window never goes fullscreen, so just return the expected
// bounds rectangle here.
if (IsHeadless()) {
return headless_mode_window_->bounds;
}

// If we're in fullscreen mode, we've changed the normal bounds to the monitor
// rect, so return the saved bounds instead.
Expand Down Expand Up @@ -630,6 +707,17 @@ void HWNDMessageHandler::SetDwmFrameExtension(DwmFrameState state) {
}

void HWNDMessageHandler::SetSize(const gfx::Size& size) {
// In headless mode update the expected window size and pretend the platform
// window size was updated.
if (IsHeadless()) {
bool size_changed = headless_mode_window_->bounds.size() != size;
headless_mode_window_->bounds.set_size(size);
if (size_changed) {
delegate_->HandleClientSizeChanged(GetClientAreaBounds().size());
}
return;
}

SetWindowPos(hwnd(), nullptr, 0, 0, size.width(), size.height(),
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
}
Expand Down Expand Up @@ -1533,9 +1621,13 @@ void HWNDMessageHandler::TrackMouseEvents(DWORD mouse_tracking_flags) {
}

void HWNDMessageHandler::ClientAreaSizeChanged() {
// Ignore size changes due to fullscreen windows losing activation.
if (background_fullscreen_hack_ && !sent_window_size_changing_)
// Ignore size changes due to fullscreen windows losing activation and
// in headless mode since it maintains expected rather than actual platform
// window bounds.
if ((background_fullscreen_hack_ && !sent_window_size_changing_) ||
IsHeadless()) {
return;
}
auto ref = msg_handler_weak_factory_.GetWeakPtr();
delegate_->HandleClientSizeChanged(GetClientAreaBounds().size());
if (!ref)
Expand Down Expand Up @@ -3618,6 +3710,17 @@ bool HWNDMessageHandler::HandleMouseInputForCaption(unsigned int message,
void HWNDMessageHandler::SetBoundsInternal(const gfx::Rect& bounds_in_pixels,
bool force_size_changed) {
gfx::Size old_size = GetClientAreaBounds().size();

// In headless update the expected window bounds and notify the delegate
// pretending the platform window size has been changed.
if (IsHeadless()) {
headless_mode_window_->bounds = bounds_in_pixels;
if (old_size != bounds_in_pixels.size() || force_size_changed) {
delegate_->HandleClientSizeChanged(GetClientAreaBounds().size());
}
return;
}

SetWindowPos(hwnd(), nullptr, bounds_in_pixels.x(), bounds_in_pixels.y(),
bounds_in_pixels.width(), bounds_in_pixels.height(),
SWP_NOACTIVATE | SWP_NOZORDER);
Expand Down
4 changes: 4 additions & 0 deletions ui/views/win/hwnd_message_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
#include "ui/base/ui_base_types.h"
#include "ui/base/win/window_event_target.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/sequential_id_generator.h"
#include "ui/gfx/win/msg_util.h"
#include "ui/gfx/win/window_impl.h"
Expand Down Expand Up @@ -826,6 +828,8 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl,
bool fullscreen_state = false;
bool active_state = false;
enum { kNormal, kMinimized, kMaximized } minmax_state = kNormal;

gfx::Rect bounds;
};

// This is present iff the window has been created in headless mode.
Expand Down

0 comments on commit fad2345

Please sign in to comment.