blob: 8bcee4721389fd9bf3767008368372092744d660 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "headless/lib/browser/headless_content_browser_client.h"
#include <memory>
#include <unordered_set>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/i18n/rtl.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "build/build_config.h"
#include "components/embedder_support/switches.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "headless/app/headless_shell_switches.h"
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_browser_main_parts.h"
#include "headless/lib/browser/headless_devtools_manager_delegate.h"
#include "headless/lib/browser/headless_quota_permission_context.h"
#include "headless/lib/headless_macros.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "net/base/url_util.h"
#include "net/ssl/client_cert_identity.h"
#include "printing/buildflags/buildflags.h"
#include "sandbox/policy/switches.h"
#include "ui/base/ui_base_switches.h"
#include "ui/gfx/switches.h"
#if defined(HEADLESS_USE_BREAKPAD)
#include "base/debug/leak_annotations.h"
#include "components/crash/content/browser/crash_handler_host_linux.h"
#include "components/crash/core/app/breakpad_linux.h"
#include "content/public/common/content_descriptors.h"
#endif // defined(HEADLESS_USE_BREAKPAD)
namespace headless {
namespace {
#if defined(HEADLESS_USE_BREAKPAD)
breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost(
const std::string& process_type,
const HeadlessBrowser::Options& options) {
base::FilePath dumps_path = options.crash_dumps_dir;
if (dumps_path.empty()) {
bool ok = base::PathService::Get(base::DIR_MODULE, &dumps_path);
DCHECK(ok);
}
{
ANNOTATE_SCOPED_MEMORY_LEAK;
#if defined(OFFICIAL_BUILD)
// Upload crash dumps in official builds, unless we're running in unattended
// mode (not to be confused with headless mode in general -- see
// chrome/common/env_vars.cc).
static const char kHeadless[] = "CHROME_HEADLESS";
bool upload = (getenv(kHeadless) == nullptr);
#else
bool upload = false;
#endif
breakpad::CrashHandlerHostLinux* crash_handler =
new breakpad::CrashHandlerHostLinux(process_type, dumps_path, upload);
crash_handler->StartUploaderThread();
return crash_handler;
}
}
int GetCrashSignalFD(const base::CommandLine& command_line,
const HeadlessBrowser::Options& options) {
if (!breakpad::IsCrashReporterEnabled())
return -1;
std::string process_type =
command_line.GetSwitchValueASCII(::switches::kProcessType);
if (process_type == ::switches::kRendererProcess) {
static breakpad::CrashHandlerHostLinux* crash_handler =
CreateCrashHandlerHost(process_type, options);
return crash_handler->GetDeathSignalSocket();
}
if (process_type == ::switches::kPpapiPluginProcess) {
static breakpad::CrashHandlerHostLinux* crash_handler =
CreateCrashHandlerHost(process_type, options);
return crash_handler->GetDeathSignalSocket();
}
if (process_type == ::switches::kGpuProcess) {
static breakpad::CrashHandlerHostLinux* crash_handler =
CreateCrashHandlerHost(process_type, options);
return crash_handler->GetDeathSignalSocket();
}
return -1;
}
#endif // defined(HEADLESS_USE_BREAKPAD)
} // namespace
// Implements a stub BadgeService. This implementation does nothing, but is
// required because inbound Mojo messages which do not have a registered
// handler are considered an error, and the render process is terminated.
// See https://crbug.com/1090429
class HeadlessContentBrowserClient::StubBadgeService
: public blink::mojom::BadgeService {
public:
StubBadgeService() = default;
StubBadgeService(const StubBadgeService&) = delete;
StubBadgeService& operator=(const StubBadgeService&) = delete;
~StubBadgeService() override = default;
void Bind(mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
receivers_.Add(this, std::move(receiver));
}
void Reset() {}
// blink::mojom::BadgeService:
void SetBadge(blink::mojom::BadgeValuePtr value) override {}
void ClearBadge() override {}
private:
mojo::ReceiverSet<blink::mojom::BadgeService> receivers_;
};
HeadlessContentBrowserClient::HeadlessContentBrowserClient(
HeadlessBrowserImpl* browser)
: browser_(browser),
append_command_line_flags_callback_(
browser_->options()->append_command_line_flags_callback) {}
HeadlessContentBrowserClient::~HeadlessContentBrowserClient() = default;
std::unique_ptr<content::BrowserMainParts>
HeadlessContentBrowserClient::CreateBrowserMainParts(
const content::MainFunctionParams& parameters) {
auto browser_main_parts =
std::make_unique<HeadlessBrowserMainParts>(parameters, browser_);
browser_->set_browser_main_parts(browser_main_parts.get());
return browser_main_parts;
}
void HeadlessContentBrowserClient::OverrideWebkitPrefs(
content::WebContents* web_contents,
blink::web_pref::WebPreferences* prefs) {
auto* browser_context =
HeadlessBrowserContextImpl::From(web_contents->GetBrowserContext());
base::RepeatingCallback<void(blink::web_pref::WebPreferences*)> callback =
browser_context->options()->override_web_preferences_callback();
if (callback)
callback.Run(prefs);
}
void HeadlessContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
content::RenderFrameHost* render_frame_host,
mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
map->Add<blink::mojom::BadgeService>(base::BindRepeating(
&HeadlessContentBrowserClient::BindBadgeService, base::Unretained(this)));
}
content::DevToolsManagerDelegate*
HeadlessContentBrowserClient::GetDevToolsManagerDelegate() {
return new HeadlessDevToolsManagerDelegate(browser_->GetWeakPtr());
}
scoped_refptr<content::QuotaPermissionContext>
HeadlessContentBrowserClient::CreateQuotaPermissionContext() {
return new HeadlessQuotaPermissionContext();
}
content::GeneratedCodeCacheSettings
HeadlessContentBrowserClient::GetGeneratedCodeCacheSettings(
content::BrowserContext* context) {
// If we pass 0 for size, disk_cache will pick a default size using the
// heuristics based on available disk size. These are implemented in
// disk_cache::PreferredCacheSize in net/disk_cache/cache_util.cc.
return content::GeneratedCodeCacheSettings(true, 0, context->GetPath());
}
#if defined(OS_POSIX) && !defined(OS_MAC)
void HeadlessContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
const base::CommandLine& command_line,
int child_process_id,
content::PosixFileDescriptorInfo* mappings) {
#if defined(HEADLESS_USE_BREAKPAD)
int crash_signal_fd = GetCrashSignalFD(command_line, *browser_->options());
if (crash_signal_fd >= 0)
mappings->Share(kCrashDumpSignal, crash_signal_fd);
#endif // defined(HEADLESS_USE_BREAKPAD)
}
#endif // defined(OS_POSIX) && !defined(OS_MAC)
void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches(
base::CommandLine* command_line,
int child_process_id) {
// NOTE: We may be called on the UI or IO thread. If called on the IO thread,
// |browser_| may have already been destroyed.
command_line->AppendSwitch(::switches::kHeadless);
const base::CommandLine& old_command_line(
*base::CommandLine::ForCurrentProcess());
if (old_command_line.HasSwitch(switches::kUserAgent)) {
command_line->AppendSwitchNative(
switches::kUserAgent,
old_command_line.GetSwitchValueNative(switches::kUserAgent));
}
#if defined(HEADLESS_USE_BREAKPAD)
// This flag tells child processes to also turn on crash reporting.
if (breakpad::IsCrashReporterEnabled())
command_line->AppendSwitch(::switches::kEnableCrashReporter);
#endif // defined(HEADLESS_USE_BREAKPAD)
if (old_command_line.HasSwitch(switches::kExportTaggedPDF))
command_line->AppendSwitch(switches::kExportTaggedPDF);
// If we're spawning a renderer, then override the language switch.
std::string process_type =
command_line->GetSwitchValueASCII(::switches::kProcessType);
if (process_type == ::switches::kRendererProcess) {
// Renderer processes are initialized on the UI thread, so this is safe.
content::RenderProcessHost* render_process_host =
content::RenderProcessHost::FromID(child_process_id);
if (render_process_host) {
HeadlessBrowserContextImpl* headless_browser_context_impl =
HeadlessBrowserContextImpl::From(
render_process_host->GetBrowserContext());
std::vector<base::StringPiece> languages = base::SplitStringPiece(
headless_browser_context_impl->options()->accept_language(), ",",
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (!languages.empty()) {
command_line->AppendSwitchASCII(::switches::kLang,
languages[0].as_string());
}
}
// Please keep this in alphabetical order.
static const char* const kSwitchNames[] = {
embedder_support::kOriginTrialDisabledFeatures,
embedder_support::kOriginTrialDisabledTokens,
embedder_support::kOriginTrialPublicKey,
};
command_line->CopySwitchesFrom(old_command_line, kSwitchNames,
base::size(kSwitchNames));
}
if (append_command_line_flags_callback_) {
HeadlessBrowserContextImpl* headless_browser_context_impl = nullptr;
if (process_type == ::switches::kRendererProcess) {
// Renderer processes are initialized on the UI thread, so this is safe.
content::RenderProcessHost* render_process_host =
content::RenderProcessHost::FromID(child_process_id);
if (render_process_host) {
headless_browser_context_impl = HeadlessBrowserContextImpl::From(
render_process_host->GetBrowserContext());
}
}
append_command_line_flags_callback_.Run(command_line,
headless_browser_context_impl,
process_type, child_process_id);
}
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
// Processes may only query perf_event_open with the BPF sandbox disabled.
if (old_command_line.HasSwitch(::switches::kEnableThreadInstructionCount) &&
old_command_line.HasSwitch(sandbox::policy::switches::kNoSandbox)) {
command_line->AppendSwitch(::switches::kEnableThreadInstructionCount);
}
#endif
}
std::string HeadlessContentBrowserClient::GetApplicationLocale() {
return base::i18n::GetConfiguredLocale();
}
std::string HeadlessContentBrowserClient::GetAcceptLangs(
content::BrowserContext* context) {
return browser_->options()->accept_language;
}
void HeadlessContentBrowserClient::AllowCertificateError(
content::WebContents* web_contents,
int cert_error,
const net::SSLInfo& ssl_info,
const GURL& request_url,
bool is_main_frame_request,
bool strict_enforcement,
base::OnceCallback<void(content::CertificateRequestResultType)> callback) {
if (!callback.is_null()) {
// If --allow-insecure-localhost is specified, and the request
// was for localhost, then the error was not fatal.
bool allow_localhost = base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kAllowInsecureLocalhost);
if (allow_localhost && net::IsLocalhost(request_url)) {
std::move(callback).Run(
content::CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE);
return;
}
std::move(callback).Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY);
}
}
base::OnceClosure HeadlessContentBrowserClient::SelectClientCertificate(
content::WebContents* web_contents,
net::SSLCertRequestInfo* cert_request_info,
net::ClientCertIdentityList client_certs,
std::unique_ptr<content::ClientCertificateDelegate> delegate) {
delegate->ContinueWithCertificate(nullptr, nullptr);
return base::OnceClosure();
}
bool HeadlessContentBrowserClient::ShouldEnableStrictSiteIsolation() {
// TODO(lukasza): https://crbug.com/869494: Instead of overriding
// ShouldEnableStrictSiteIsolation, //headless should inherit the default
// site-per-process setting from //content - this way tools (tests, but also
// production cases like screenshot or pdf generation) based on //headless
// will use a mode that is actually shipping in Chrome.
return browser_->options()->site_per_process;
}
void HeadlessContentBrowserClient::ConfigureNetworkContextParams(
content::BrowserContext* context,
bool in_memory,
const base::FilePath& relative_partition_path,
::network::mojom::NetworkContextParams* network_context_params,
::network::mojom::CertVerifierCreationParams*
cert_verifier_creation_params) {
HeadlessBrowserContextImpl::From(context)->ConfigureNetworkContextParams(
in_memory, relative_partition_path, network_context_params,
cert_verifier_creation_params);
}
std::string HeadlessContentBrowserClient::GetProduct() {
return browser_->options()->product_name_and_version;
}
std::string HeadlessContentBrowserClient::GetUserAgent() {
return browser_->options()->user_agent;
}
void HeadlessContentBrowserClient::BindBadgeService(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
if (!stub_badge_service_)
stub_badge_service_ = std::make_unique<StubBadgeService>();
stub_badge_service_->Bind(std::move(receiver));
}
} // namespace headless