blob: a069eaef4962cd670a1228dec826c29c7ba23996 [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 "headless/lib/browser/command_line_handler.h"
#include <cstdio>
#include <string_view>
#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
#include "components/headless/screen_info/headless_screen_info.h"
#include "components/viz/common/switches.h"
#include "content/public/common/content_switches.h"
#include "headless/public/switches.h"
#include "net/http/http_util.h"
#include "net/proxy_resolution/proxy_config.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/gfx/font_render_params.h"
#include "ui/gfx/geometry/size.h"
namespace headless {
namespace {
void AppendSwitchMaybe(base::CommandLine& command_line,
std::string_view switch_constant) {
if (!command_line.HasSwitch(switch_constant)) {
command_line.AppendSwitch(switch_constant);
}
}
void HandleDeterministicModeSwitch(base::CommandLine& command_line) {
DCHECK(command_line.HasSwitch(switches::kDeterministicMode));
AppendSwitchMaybe(command_line, switches::kEnableBeginFrameControl);
// Compositor flags
AppendSwitchMaybe(command_line,
::switches::kRunAllCompositorStagesBeforeDraw);
AppendSwitchMaybe(command_line,
::switches::kDisableNewContentRenderingTimeout);
// Ensure that image animations don't resync their animation timestamps when
// looping back around.
AppendSwitchMaybe(command_line,
blink::switches::kDisableImageAnimationResync);
// Renderer flags
AppendSwitchMaybe(command_line, ::switches::kDisableThreadedAnimation);
AppendSwitchMaybe(command_line, ::switches::kDisableCheckerImaging);
}
bool HandleRemoteDebuggingPort(base::CommandLine& command_line,
HeadlessBrowser::Options& options) {
DCHECK(command_line.HasSwitch(::switches::kRemoteDebuggingPort));
int port;
std::string port_str =
command_line.GetSwitchValueASCII(::switches::kRemoteDebuggingPort);
if (!base::StringToInt(port_str, &port) ||
!base::IsValueInRangeForNumericType<uint16_t>(port)) {
LOG(ERROR) << "Invalid devtools server port: " << port_str;
return false;
}
options.devtools_port = base::checked_cast<uint16_t>(port);
return true;
}
void HandleProxyServer(base::CommandLine& command_line,
HeadlessBrowser::Options& options) {
DCHECK(command_line.HasSwitch(switches::kProxyServer));
std::string proxy_server =
command_line.GetSwitchValueASCII(switches::kProxyServer);
auto proxy_config = std::make_unique<net::ProxyConfig>();
proxy_config->proxy_rules().ParseFromString(proxy_server);
if (command_line.HasSwitch(switches::kProxyBypassList)) {
std::string bypass_list =
command_line.GetSwitchValueASCII(switches::kProxyBypassList);
proxy_config->proxy_rules().bypass_rules.ParseFromString(bypass_list);
}
options.proxy_config = std::move(proxy_config);
}
bool HandleWindowSize(base::CommandLine& command_line,
HeadlessBrowser::Options& options) {
DCHECK(command_line.HasSwitch(switches::kWindowSize));
const std::string switch_value =
command_line.GetSwitchValueASCII(switches::kWindowSize);
int width = 0;
int height = 0;
int n =
UNSAFE_TODO(sscanf(switch_value.c_str(), "%d%*[x,]%d", &width, &height));
if (n != 2 || width < 0 || height < 0) {
LOG(ERROR) << "Malformed window size: " << switch_value;
return false;
}
options.window_size = gfx::Size(width, height);
return true;
}
bool HandleScreenInfo(base::CommandLine& command_line,
HeadlessBrowser::Options& options) {
DCHECK(command_line.HasSwitch(switches::kScreenInfo));
const std::string switch_value =
command_line.GetSwitchValueASCII(switches::kScreenInfo);
auto screen_info = HeadlessScreenInfo::FromString(switch_value);
if (!screen_info.has_value()) {
LOG(ERROR) << screen_info.error();
return false;
}
options.screen_info_spec = switch_value;
return true;
}
bool HandleFontRenderHinting(base::CommandLine& command_line,
HeadlessBrowser::Options& options) {
std::string switch_value =
command_line.GetSwitchValueASCII(switches::kFontRenderHinting);
gfx::FontRenderParams::Hinting font_render_hinting;
static_assert(gfx::FontRenderParams::Hinting::HINTING_MAX == 3);
if (switch_value == "full") {
font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_FULL;
} else if (switch_value == "medium") {
font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_MEDIUM;
} else if (switch_value == "slight") {
font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_SLIGHT;
} else if (switch_value == "none") {
font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_NONE;
} else {
LOG(ERROR) << "Unknown font-render-hinting parameter value";
return false;
}
options.font_render_hinting = font_render_hinting;
return true;
}
base::FilePath EnsureDirectoryExists(const base::FilePath& file_path) {
if (!base::DirectoryExists(file_path) && !base::CreateDirectory(file_path)) {
PLOG(ERROR) << "Could not create directory " << file_path;
return base::FilePath();
}
if (file_path.IsAbsolute()) {
return file_path;
}
const base::FilePath absolute_file_path =
base::MakeAbsoluteFilePath(file_path);
if (absolute_file_path.empty()) {
PLOG(ERROR) << "Invalid directory path " << file_path;
return base::FilePath();
}
return absolute_file_path;
}
} // namespace
bool HandleCommandLineSwitches(base::CommandLine& command_line,
HeadlessBrowser::Options& options) {
if (command_line.HasSwitch(switches::kDeterministicMode)) {
if (command_line.HasSwitch(::switches::kSitePerProcess)) {
LOG(ERROR) << "Deterministic mode is not compatible with --"
<< ::switches::kSitePerProcess << " switch.";
return false;
}
HandleDeterministicModeSwitch(command_line);
}
if (command_line.HasSwitch(switches::kEnableBeginFrameControl)) {
if (command_line.HasSwitch(::switches::kSitePerProcess)) {
LOG(ERROR) << "Frame control is not compatible with --"
<< ::switches::kSitePerProcess << " switch.";
return false;
}
options.enable_begin_frame_control = true;
}
if (command_line.HasSwitch(::switches::kRemoteDebuggingPort)) {
if (!HandleRemoteDebuggingPort(command_line, options)) {
return false;
}
}
if (command_line.HasSwitch(::switches::kRemoteDebuggingPipe)) {
options.devtools_pipe_enabled = true;
}
if (command_line.HasSwitch(switches::kProxyServer)) {
HandleProxyServer(command_line, options);
}
if (command_line.HasSwitch(switches::kUserDataDir)) {
const base::FilePath dir = EnsureDirectoryExists(
command_line.GetSwitchValuePath(switches::kUserDataDir));
if (dir.empty()) {
return false;
}
options.user_data_dir = dir;
if (!command_line.HasSwitch(switches::kIncognito)) {
options.incognito_mode = false;
}
}
if (command_line.HasSwitch(switches::kDiskCacheDir)) {
const base::FilePath dir = EnsureDirectoryExists(
command_line.GetSwitchValuePath(switches::kDiskCacheDir));
if (dir.empty()) {
return false;
}
options.disk_cache_dir = dir;
}
if (command_line.HasSwitch(switches::kWindowSize)) {
if (!HandleWindowSize(command_line, options)) {
return false;
}
}
if (command_line.HasSwitch(switches::kScreenInfo)) {
if (!HandleScreenInfo(command_line, options)) {
return false;
}
}
if (command_line.HasSwitch(switches::kUserAgent)) {
std::string user_agent =
command_line.GetSwitchValueASCII(switches::kUserAgent);
if (net::HttpUtil::IsValidHeaderValue(user_agent)) {
options.user_agent = user_agent;
}
}
if (command_line.HasSwitch(switches::kAcceptLang)) {
options.accept_language =
command_line.GetSwitchValueASCII(switches::kAcceptLang);
}
if (command_line.HasSwitch(switches::kFontRenderHinting)) {
if (!HandleFontRenderHinting(command_line, options)) {
return false;
}
}
if (command_line.HasSwitch(switches::kBlockNewWebContents)) {
options.block_new_web_contents = true;
}
if (command_line.HasSwitch(switches::kDisableLazyLoading)) {
options.lazy_load_enabled = false;
}
if (command_line.HasSwitch(switches::kForceNewBrowsingInstance)) {
options.force_new_browsing_instance = true;
}
return true;
}
} // namespace headless