0

[headless] Add active field trials support to headless_shell

This CL adds limited field trials support to headless_shell:
the active field trials defined in fieldtrial_testing_config.json
can be loaded if --enable-field-trial-config switch is specified.

Note that variations service is not instantiated so dynamic field
trials configuration is not available and cannot be enabled even
if --variations-server-url switch is specified.

Bug: 41486180
Change-Id: I55d88025ee7686645a050a515f3820013dd989e7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5555887
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Reviewed-by: Luc Nguyen <lucnguyen@google.com>
Reviewed-by: Dominic Battre <battre@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1310053}
This commit is contained in:
Peter Kvitek
2024-06-04 18:42:29 +00:00
committed by Chromium LUCI CQ
parent 861ebfd801
commit 1a415977a2
9 changed files with 296 additions and 36 deletions

@ -33,6 +33,9 @@ if (headless_enable_commands) {
bundle_hyphen_data = (is_linux || is_win) && !headless_use_embedded_resources
# If supported, field trials need to be enabled by --enable-field-trial-config switch.
headless_support_field_trials = headless_use_prefs
# Headless defines config applied to every target below.
config("headless_defines_config") {
defines = []
@ -52,6 +55,10 @@ config("headless_defines_config") {
if (headless_mode_policy_supported) {
defines += [ "HEADLESS_MODE_POLICY_SUPPORTED" ]
}
if (headless_support_field_trials) {
defines += [ "HEADLESS_SUPPORT_FIELD_TRIALS" ]
}
}
# For code inside the build component "headless".
@ -389,6 +396,18 @@ component("headless_non_renderer") {
]
}
if (headless_support_field_trials) {
sources += [
"lib/browser/headless_field_trials.cc",
"lib/browser/headless_field_trials.h",
]
deps += [
"//components/metrics",
"//components/variations",
"//components/variations/service",
]
}
if (enable_printing) {
deps += [
"//components/printing/browser",

@ -9,13 +9,14 @@ specific_include_rules = {
"+services/device/public/cpp/geolocation/geolocation_manager_impl_mac.h",
],
"headless_content_client.cc": [
"+components/embedder_support/origin_trials",
"+components/embedder_support/origin_trials",
],
"headless_content_main_delegate.cc": [
"+gpu/config/gpu_switches.h",
"+cc/base/switches.h",
"+components/viz/common/switches.h",
"+components/devtools/devtools_pipe/devtools_pipe.h",
"+gpu/config/gpu_switches.h",
"+components/prefs",
"+third_party/blink/public/common/switches.h",
],
}

@ -38,6 +38,8 @@ specific_include_rules = {
"+components/prefs",
"+components/pref_registry",
"+components/keyed_service/content",
"+components/metrics",
"+components/variations",
],
"headless_browser_impl_unittest.cc": [
"+third_party/blink/public/common/features.h",
@ -58,5 +60,10 @@ specific_include_rules = {
],
"command_line_handler.cc": [
"+third_party/blink/public/common/switches.h",
],
"headless_field_trials.cc": [
"+components/prefs",
"+components/metrics",
"+components/variations",
]
}

@ -45,6 +45,11 @@
#include "headless/lib/browser/policy/headless_policies.h"
#endif
#if defined(HEADLESS_SUPPORT_FIELD_TRIALS)
#include "components/metrics/metrics_service.h" // nogncheck
#include "components/variations/service/variations_service.h" // nogncheck
#endif
namespace headless {
namespace {
@ -344,9 +349,6 @@ bool HeadlessBrowserImpl::ShouldStartDevToolsServer() {
void HeadlessBrowserImpl::PreMainMessageLoopRun() {
PlatformInitialize();
#if defined(HEADLESS_USE_PREFS)
CreatePrefService();
#endif
// We don't support the tethering domain on this agent host.
agent_host_ = content::DevToolsAgentHost::CreateForBrowser(
@ -375,12 +377,6 @@ void HeadlessBrowserImpl::PostMainMessageLoopRun() {
#endif
}
#if defined(HEADLESS_USE_PREFS)
PrefService* HeadlessBrowserImpl::GetPrefs() {
return local_state_.get();
}
#endif
#if defined(HEADLESS_USE_POLICY)
policy::PolicyService* HeadlessBrowserImpl::GetPolicyService() {
return policy_connector_ ? policy_connector_->GetPolicyService() : nullptr;
@ -389,6 +385,8 @@ policy::PolicyService* HeadlessBrowserImpl::GetPolicyService() {
#if defined(HEADLESS_USE_PREFS)
void HeadlessBrowserImpl::CreatePrefService() {
CHECK(!local_state_);
scoped_refptr<PersistentPrefStore> pref_store;
if (options()->user_data_dir.empty()) {
pref_store = base::MakeRefCounted<InMemoryPrefStore>();
@ -421,6 +419,11 @@ void HeadlessBrowserImpl::CreatePrefService() {
OSCrypt::RegisterLocalPrefs(pref_registry.get());
#endif
#if defined(HEADLESS_SUPPORT_FIELD_TRIALS)
metrics::MetricsService::RegisterPrefs(pref_registry.get());
variations::VariationsService::RegisterPrefs(pref_registry.get());
#endif
PrefServiceFactory factory;
#if defined(HEADLESS_USE_POLICY)
@ -447,6 +450,10 @@ void HeadlessBrowserImpl::CreatePrefService() {
}
#endif // BUILDFLAG(IS_WIN)
}
PrefService* HeadlessBrowserImpl::GetPrefs() {
return local_state_.get();
}
#endif // defined(HEADLESS_USE_PREFS)
} // namespace headless

@ -113,6 +113,7 @@ class HEADLESS_EXPORT HeadlessBrowserImpl : public HeadlessBrowser {
int exit_code() const { return exit_code_; }
#if defined(HEADLESS_USE_PREFS)
void CreatePrefService();
PrefService* GetPrefs();
#endif
@ -129,10 +130,6 @@ class HEADLESS_EXPORT HeadlessBrowserImpl : public HeadlessBrowser {
#endif
private:
#if defined(HEADLESS_USE_PREFS)
void CreatePrefService();
#endif
base::OnceCallback<void(HeadlessBrowser*)> on_start_callback_;
std::optional<HeadlessBrowser::Options> options_;

@ -0,0 +1,135 @@
// Copyright 2024 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/headless_field_trials.h"
#include <functional>
#include <memory>
#include <vector>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "components/metrics/enabled_state_provider.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/prefs/pref_service.h"
#include "components/variations/platform_field_trials.h"
#include "components/variations/seed_response.h"
#include "components/variations/service/safe_seed_manager.h"
#include "components/variations/service/ui_string_overrider.h"
#include "components/variations/service/variations_field_trial_creator.h"
#include "components/variations/service/variations_service_client.h"
#include "components/variations/variations_safe_seed_store_local_state.h"
#include "components/variations/variations_seed_store.h"
#include "components/variations/variations_switches.h"
#include "content/public/common/content_switch_dependent_feature_overrides.h"
namespace headless {
namespace {
// A simple concrete implementation of the EnabledStateProvider interface.
class HeadlessEnabledStateProvider : public metrics::EnabledStateProvider {
public:
HeadlessEnabledStateProvider(bool consent, bool enabled)
: consent_(consent), enabled_(enabled) {}
HeadlessEnabledStateProvider(const HeadlessEnabledStateProvider&) = delete;
HeadlessEnabledStateProvider& operator=(const HeadlessEnabledStateProvider&) =
delete;
~HeadlessEnabledStateProvider() override = default;
// metrics::EnabledStateProvider
bool IsConsentGiven() const override { return consent_; }
bool IsReportingEnabled() const override { return enabled_; }
private:
bool consent_;
bool enabled_;
};
class HeadlessVariationsServiceClient
: public variations::VariationsServiceClient {
public:
HeadlessVariationsServiceClient() = default;
~HeadlessVariationsServiceClient() override = default;
// variations::VariationsServiceClient:
base::Version GetVersionForSimulation() override { return base::Version(); }
scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
override {
return nullptr;
}
network_time::NetworkTimeTracker* GetNetworkTimeTracker() override {
return nullptr;
}
version_info::Channel GetChannel() override {
return version_info::Channel::UNKNOWN;
}
bool OverridesRestrictParameter(std::string* parameter) override {
return false;
}
bool IsEnterprise() override { return false; }
void RemoveGoogleGroupsFromPrefsForDeletedProfiles(
PrefService* local_state) override {}
};
} // namespace
bool ShouldEnableFieldTrials() {
static bool should_enable_field_trials =
base::CommandLine::ForCurrentProcess()->HasSwitch(
variations::switches::kEnableFieldTrialTestingConfig);
return should_enable_field_trials;
}
void SetUpFieldTrials(PrefService* local_state,
const base::FilePath& user_data_dir) {
CHECK(local_state);
HeadlessEnabledStateProvider enabled_state_provider(/*consent=*/false,
/*enabled=*/false);
std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager =
metrics::MetricsStateManager::Create(local_state, &enabled_state_provider,
std::wstring(), user_data_dir);
metrics_state_manager->InstantiateFieldTrialList();
HeadlessVariationsServiceClient variations_service_client;
variations::VariationsFieldTrialCreator field_trial_creator(
&variations_service_client,
std::make_unique<variations::VariationsSeedStore>(
local_state, /*initial_seed=*/nullptr,
/*signature_verification_enabled=*/true,
std::make_unique<variations::VariationsSafeSeedStoreLocalState>(
local_state)),
variations::UIStringOverrider(),
/*limited_entropy_synthetic_trial=*/nullptr);
variations::SafeSeedManager safe_seed_manager(local_state);
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
// Overrides for content/common and lower layers' switches.
std::vector<base::FeatureList::FeatureOverrideInfo> feature_overrides =
content::GetSwitchDependentFeatureOverrides(command_line);
// Setup field trials directly without help of the variations service.
std::vector<std::string> variation_ids;
auto feature_list = std::make_unique<base::FeatureList>();
variations::PlatformFieldTrials platform_field_trials;
variations::SyntheticTrialRegistry synthetic_trial_registry;
field_trial_creator.SetUpFieldTrials(
variation_ids,
command_line.GetSwitchValueASCII(
variations::switches::kForceVariationIds),
feature_overrides, std::move(feature_list), metrics_state_manager.get(),
&synthetic_trial_registry, &platform_field_trials, &safe_seed_manager,
/*add_entropy_source_to_variations_ids=*/false);
}
} // namespace headless

@ -0,0 +1,24 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef HEADLESS_LIB_BROWSER_HEADLESS_FIELD_TRIALS_H_
#define HEADLESS_LIB_BROWSER_HEADLESS_FIELD_TRIALS_H_
#include "base/files/file_path.h"
class PrefService;
namespace headless {
// Returns positive if --enable-field-trial-config switch is specified in the
// command line.
bool ShouldEnableFieldTrials();
// Instantiates metrics state manager and sets up field trials.
void SetUpFieldTrials(PrefService* local_state,
const base::FilePath& user_data_dir);
} // namespace headless
#endif // HEADLESS_LIB_BROWSER_HEADLESS_FIELD_TRIALS_H_

@ -69,6 +69,16 @@
#include <signal.h>
#endif
#if defined(HEADLESS_USE_PREFS)
#include "components/prefs/pref_service.h"
#endif
#if defined(HEADLESS_SUPPORT_FIELD_TRIALS)
#include "content/public/app/initialize_mojo_core.h"
#include "headless/lib/browser/headless_field_trials.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#endif
namespace headless {
namespace features {
@ -174,6 +184,33 @@ void InitApplicationLocale(const base::CommandLine& command_line) {
command_line.GetSwitchValueASCII(::switches::kLang));
}
void AddSwitchesForVirtualTime() {
// Only pass viz flags into the virtual time mode.
const char* const switches[] = {
// TODO(eseckler): Make --run-all-compositor-stages-before-draw a
// per-BeginFrame mode so that we can activate it for individual
// requests
// only. With surface sync becoming the default, we can then make
// virtual_time_enabled a per-request option, too.
// We control BeginFrames ourselves and need all compositing stages to
// run.
::switches::kRunAllCompositorStagesBeforeDraw,
::switches::kDisableNewContentRenderingTimeout,
cc::switches::kDisableThreadedAnimation,
// Animtion-only BeginFrames are only supported when updates from the
// impl-thread are disabled, see go/headless-rendering.
cc::switches::kDisableCheckerImaging,
// Ensure that image animations don't resync their animation timestamps
// when looping back around.
blink::switches::kDisableImageAnimationResync,
};
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
for (const auto* flag : switches) {
command_line->AppendSwitch(flag);
}
}
} // namespace
HeadlessContentMainDelegate::HeadlessContentMainDelegate(
@ -528,31 +565,58 @@ std::optional<int> HeadlessContentMainDelegate::PostEarlyInitialization(
if (absl::holds_alternative<InvokedInChildProcess>(invoked_in))
return std::nullopt;
#if defined(HEADLESS_USE_PREFS)
browser_->CreatePrefService();
#endif
#if defined(HEADLESS_SUPPORT_FIELD_TRIALS)
// Check if we're telling content to not to create the feature list and do it
// here if so. Content can create default feature list on its own however here
// we want the feature list to be created by field trial machinery.
if (!ShouldCreateFeatureList(invoked_in)) {
SetUpFieldTrials(browser()->GetPrefs(),
browser()->options()->user_data_dir);
// Schedule a Local State write since the above function may have resulted
// in some prefs being updated. Headless shell runs are typically short and
// often end in crashes, so it helps to commit early.
browser_->GetPrefs()->CommitPendingWrite();
}
// Check if we're telling content to not to initialize Mojo and do it here
// since we want it do be done after the feature list is created.
if (!ShouldInitializeMojo(invoked_in)) {
content::InitializeMojoCore();
}
#endif // defined(HEADLESS_SUPPORT_FIELD_TRIALS)
if (base::FeatureList::IsEnabled(features::kVirtualTime)) {
// Only pass viz flags into the virtual time mode.
const char* const switches[] = {
// TODO(eseckler): Make --run-all-compositor-stages-before-draw a
// per-BeginFrame mode so that we can activate it for individual
// requests
// only. With surface sync becoming the default, we can then make
// virtual_time_enabled a per-request option, too.
// We control BeginFrames ourselves and need all compositing stages to
// run.
::switches::kRunAllCompositorStagesBeforeDraw,
::switches::kDisableNewContentRenderingTimeout,
cc::switches::kDisableThreadedAnimation,
// Animtion-only BeginFrames are only supported when updates from the
// impl-thread are disabled, see go/headless-rendering.
cc::switches::kDisableCheckerImaging,
// Ensure that image animations don't resync their animation timestamps
// when looping back around.
blink::switches::kDisableImageAnimationResync,
};
for (const auto* flag : switches)
base::CommandLine::ForCurrentProcess()->AppendSwitch(flag);
AddSwitchesForVirtualTime();
}
return std::nullopt;
}
#if defined(HEADLESS_SUPPORT_FIELD_TRIALS)
bool HeadlessContentMainDelegate::ShouldCreateFeatureList(
InvokedIn invoked_in) {
// The content layer is always responsible for creating the FeatureList in
// child processes.
if (absl::holds_alternative<InvokedInChildProcess>(invoked_in)) {
return true;
}
// VariationsFieldTrialCreator::SetUpFieldTrials() instantiates its own
// feature list so prevent content from instantiating a default one if we're
// going to set up field trials.
return !ShouldEnableFieldTrials();
}
bool HeadlessContentMainDelegate::ShouldInitializeMojo(InvokedIn invoked_in) {
// Mojo cannot be initialized without a feature list instance available so
// postpone its initialization until after feature list is instantiated by
// field trials setup if field trials are enabled.
return ShouldCreateFeatureList(invoked_in);
}
#endif // defined(HEADLESS_SUPPORT_FIELD_TRIALS)
} // namespace headless

@ -55,6 +55,12 @@ class HEADLESS_EXPORT HeadlessContentMainDelegate
content::ContentRendererClient* CreateContentRendererClient() override;
std::optional<int> PostEarlyInitialization(InvokedIn invoked_in) override;
#if defined(HEADLESS_SUPPORT_FIELD_TRIALS)
bool ShouldCreateFeatureList(InvokedIn invoked_in) override;
bool ShouldInitializeMojo(InvokedIn invoked_in) override;
#endif
#if BUILDFLAG(IS_MAC)
void PlatformPreBrowserMain();
#endif