910e62b5创建于 1月15日历史提交
// 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