0

[headless] Add --screen-info handling to Headless Mode on Linux

https://crrev.com/c/6021359 added --screen-info switch support
to Chrome Headless Shell.

This CL adds the same functionality to Chrome running in
Headless mode (aka new Headless) allowing headless screen
configuration and multiple screens support.

Bug: 396072563
Change-Id: I044347052a656d25843556e145aea76fb4b97000
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6287167
Reviewed-by: Avi Drissman <avi@chromium.org>
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1423544}
This commit is contained in:
Peter Kvitek
2025-02-21 22:05:42 -08:00
committed by Chromium LUCI CQ
parent b1e640d046
commit 32fd08d611
12 changed files with 165 additions and 10 deletions

@ -138,6 +138,7 @@ if (!is_android) {
data = [
"test/data/",
"//third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js",
"//third_party/blink/web_tests/http/tests/inspector-protocol/resources/http-interceptor.js",
]
}
}

@ -341,4 +341,14 @@ HEADLESS_MODE_PROTOCOL_TEST_WITH_COMMAND_LINE_EXTRAS(
"--ozone-override-screen-size=1234,5678")
#endif
// --screen-info switch is only supported on Linux at this time.
#if BUILDFLAG(IS_LINUX)
// This currently results in an unexpected screen orientation type,
// see http://crbug.com/398150465.
HEADLESS_MODE_PROTOCOL_TEST_WITH_COMMAND_LINE_EXTRAS(
MultipleScreenDetails,
"sanity/multiple-screen-details.js",
"--screen-info={label=#1}{600x800 label=#2}")
#endif
} // namespace headless

@ -18,6 +18,7 @@
#include "chrome/browser/headless/headless_mode_switches.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/common/content_switches.h"
#include "ui/base/ui_base_switches.h"
#if BUILDFLAG(IS_LINUX)
#include "ui/gl/gl_switches.h" // nogncheck
@ -98,7 +99,8 @@ class HeadlessModeHandleImpl : public HeadlessModeHandle {
// Headless mode on Linux relies on ozone/headless platform.
command_line->AppendSwitchASCII(::switches::kOzonePlatform,
switches::kHeadless);
if (!command_line->HasSwitch(switches::kOzoneOverrideScreenSize)) {
if (!command_line->HasSwitch(switches::kScreenInfo) &&
!command_line->HasSwitch(switches::kOzoneOverrideScreenSize)) {
command_line->AppendSwitchASCII(switches::kOzoneOverrideScreenSize,
"800,600");
}

@ -0,0 +1,23 @@
Tests multiple screen configuration.
Screen
label='#1'
0,0 800x600
avail=0,0 800x600
isPrimary=true
isExtended=true
isInternal=false
colorDepth=24
devicePixelRatio=1
orientation.type=landscape-primary
orientation.angle=0
Screen
label='#2'
800,0 600x800
avail=800,0 600x800
isPrimary=false
isExtended=true
isInternal=false
colorDepth=24
devicePixelRatio=1
orientation.type=landscape-primary
orientation.angle=0

@ -0,0 +1,45 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function(testRunner) {
const {session, dp} =
await testRunner.startBlank(`Tests multiple screen configuration.`);
const HttpInterceptor =
await testRunner.loadScriptAbsolute('../resources/http-interceptor.js');
const httpInterceptor = await (new HttpInterceptor(testRunner, dp)).init();
httpInterceptor.setDisableRequestedUrlsLogging(true);
httpInterceptor.addResponse(
'https://example.com/index.html',
'<html><head><link rel="icon" href="data:,"></head></html>');
await dp.Browser.grantPermissions({permissions: ['windowManagement']});
await session.navigate('https://example.com/index.html');
const result = await session.evaluateAsync(async () => {
const screenDetails = await getScreenDetails();
const screenDetailed = screenDetails.screens.map(s => {
const lines = [
`Screen`,
` label='${s.label}'`,
` ${s.left},${s.top} ${s.width}x${s.height}`,
` avail=${s.availLeft},${s.availTop} ${s.availWidth}x${s.availHeight}`,
` isPrimary=${s.isPrimary}`,
` isExtended=${s.isExtended}`,
` isInternal=${s.isInternal}`,
` colorDepth=${s.colorDepth}`,
` devicePixelRatio=${s.devicePixelRatio}`,
` orientation.type=${s.orientation.type}`,
` orientation.angle=${s.orientation.angle}`,
];
return lines.join('\n');
});
return screenDetailed.join('\n');
});
testRunner.log(result);
testRunner.completeTest();
})

@ -114,8 +114,8 @@ inline constexpr char kProxyBypassList[] = "proxy-bypass-list";
// affects HTTP and HTTPS requests.
inline constexpr char kProxyServer[] = "proxy-server";
// Headless screen info in the format: {0,0 800x600},{800,0 600x800}.
// See //headless/lib/browser/headless_screen_info.h for more details.
// Headless screen info in the format: {0,0 800x600}{800,0 600x800}.
// See //components/headless/screen_info/headless_screen_info.h for details.
inline constexpr char kScreenInfo[] = "screen-info";
// A string used to override the default user agent with a custom one.

@ -106,4 +106,8 @@ const char kUIDisablePartialSwap[] = "ui-disable-partial-swap";
// Enables the ozone x11 clipboard for linux-chromeos.
const char kUseSystemClipboard[] = "use-system-clipboard";
// Headless screen info in the format: {0,0 800x600}{800,0 600x800}.
// See //components/headless/screen_info/headless_screen_info.h for details.
const char kScreenInfo[] = "screen-info";
} // namespace switches

@ -48,6 +48,9 @@ COMPONENT_EXPORT(UI_BASE) extern const char kTopChromeTouchUiEnabled[];
COMPONENT_EXPORT(UI_BASE) extern const char kUIDisablePartialSwap[];
COMPONENT_EXPORT(UI_BASE) extern const char kUseSystemClipboard[];
// Headless related.
COMPONENT_EXPORT(UI_BASE) extern const char kScreenInfo[];
// Test related.
COMPONENT_EXPORT(UI_BASE) extern const char kDisallowNonExactResourceReuse[];
COMPONENT_EXPORT(UI_BASE) extern const char kMangleLocalizedStrings[];

@ -27,6 +27,7 @@ source_set("headless") {
deps = [
"//base",
"//build:chromecast_buildflags",
"//components/headless/screen_info",
"//gpu/vulkan:buildflags",
"//skia",
"//ui/base",

@ -1,3 +1,4 @@
include_rules = [
"+third_party/skia/include",
"+components/headless/screen_info",
]

@ -7,17 +7,32 @@
#include <string_view>
#include <vector>
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/not_fatal_until.h"
#include "base/containers/flat_set.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "components/headless/screen_info/headless_screen_info.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/display_finder.h"
#include "ui/display/util/display_util.h"
#include "ui/ozone/public/ozone_switches.h"
using headless::HeadlessScreenInfo;
namespace ui {
namespace {
// By default headless screen has 1x1 size and 1.0 scale factor. Headless
// screen size can be overridden using --ozone-override-screen-size switch.
//
// More complex headless screen configuration (including multiple screens)
// can be specified using the --screen-info command line switch.
// See //components/headless/screen_info/headless_screen_info.h for details.
// Ozone/headless display defaults.
constexpr int64_t kHeadlessDisplayId = 1;
constexpr int64_t kHeadlessDisplayIdBase = 1;
constexpr float kHeadlessDisplayScale = 1.0f;
constexpr gfx::Size kHeadlessDisplaySize(1, 1);
@ -36,11 +51,11 @@ bool ParseScreenSize(const std::string& screen_size, int* width, int* height) {
return true;
}
gfx::Rect GetDisplayBounds() {
gfx::Rect GetHeadlessDisplayBounds() {
gfx::Rect bounds(kHeadlessDisplaySize);
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
CHECK_DEREF(base::CommandLine::ForCurrentProcess());
if (command_line.HasSwitch(switches::kOzoneOverrideScreenSize)) {
int width, height;
std::string screen_size =
@ -53,12 +68,60 @@ gfx::Rect GetDisplayBounds() {
return bounds;
}
std::vector<HeadlessScreenInfo> GetScreenInfo() {
std::vector<HeadlessScreenInfo> screen_info;
const base::CommandLine& command_line =
CHECK_DEREF(base::CommandLine::ForCurrentProcess());
if (command_line.HasSwitch(switches::kScreenInfo)) {
const std::string switch_value =
command_line.GetSwitchValueASCII(switches::kScreenInfo);
auto screen_info_or_error = HeadlessScreenInfo::FromString(switch_value);
CHECK(screen_info_or_error.has_value()) << screen_info_or_error.error();
screen_info = screen_info_or_error.value();
} else {
screen_info.push_back(
HeadlessScreenInfo({.bounds = GetHeadlessDisplayBounds(),
.device_pixel_ratio = kHeadlessDisplayScale}));
}
return screen_info;
}
} // namespace
HeadlessScreen::HeadlessScreen() {
display::Display display(kHeadlessDisplayId);
display.SetScaleAndBounds(kHeadlessDisplayScale, GetDisplayBounds());
display_list_.AddDisplay(display, display::DisplayList::Type::PRIMARY);
std::vector<HeadlessScreenInfo> screen_info = GetScreenInfo();
base::flat_set<int64_t> internal_display_ids;
display::DisplayList::Type type = display::DisplayList::Type::PRIMARY;
for (const auto& it : screen_info) {
static int64_t synthesized_display_id = kHeadlessDisplayIdBase;
display::Display display(synthesized_display_id++);
display.set_label(it.label);
display.set_color_depth(it.color_depth);
display.SetScaleAndBounds(it.device_pixel_ratio, it.bounds);
if (!it.work_area_insets.IsEmpty()) {
display.UpdateWorkAreaFromInsets(it.work_area_insets);
}
if (it.rotation) {
CHECK(display::Display::IsValidRotation(it.rotation));
display.SetRotationAsDegree(it.rotation);
}
if (it.is_internal) {
internal_display_ids.insert(display.id());
}
is_natural_landscape_map_.insert({display.id(), display.is_landscape()});
display_list_.AddDisplay(display, type);
type = display::DisplayList::Type::NOT_PRIMARY;
}
display::SetInternalDisplayIds(std::move(internal_display_ids));
}
HeadlessScreen::~HeadlessScreen() = default;

@ -7,6 +7,7 @@
#include <vector>
#include "base/containers/flat_map.h"
#include "ui/display/display_list.h"
#include "ui/gfx/geometry/point.h"
#include "ui/ozone/public/platform_screen.h"
@ -38,6 +39,7 @@ class HeadlessScreen : public PlatformScreen {
void RemoveObserver(display::DisplayObserver* observer) override;
private:
display::DisplayList display_list_;
base::flat_map<int64_t, bool> is_natural_landscape_map_;
};
} // namespace ui