[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:

committed by
Chromium LUCI CQ

parent
861ebfd801
commit
1a415977a2
@ -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_;
|
||||
|
||||
|
135
headless/lib/browser/headless_field_trials.cc
Normal file
135
headless/lib/browser/headless_field_trials.cc
Normal file
@ -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
|
24
headless/lib/browser/headless_field_trials.h
Normal file
24
headless/lib/browser/headless_field_trials.h
Normal file
@ -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
|
||||
|
Reference in New Issue
Block a user