0

Include synthetic field trials in crash reports for non-browser processes

The docs: https://docs.google.com/document/d/1SrgNGL59FAHs6gxQ50QzAHlmfZoLUxBiGvh0Khks6sE/edit?usp=sharing

Bug: 1472291
Change-Id: I966ff171daba3d5c06746782bd3ac28435241562
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4917143
Reviewed-by: Colin Blundell <blundell@chromium.org>
Commit-Queue: Takashi Sakamoto <tasak@google.com>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1229211}
This commit is contained in:
Takashi Sakamoto
2023-11-27 09:12:31 +00:00
committed by Chromium LUCI CQ
parent a5303fc64f
commit f9daf0ecfc
20 changed files with 597 additions and 5 deletions

@ -176,6 +176,7 @@
#include "content/public/browser/first_party_sets_handler.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/synthetic_trial_syncer.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
@ -677,6 +678,8 @@ void ChromeBrowserMainParts::SetupMetrics() {
variations::VariationsIdsProvider::GetInstance());
metrics->GetSyntheticTrialRegistry()->AddObserver(
variations::SyntheticTrialsActiveGroupIdProvider::GetInstance());
synthetic_trial_syncer_ = content::SyntheticTrialSyncer::Create(
metrics->GetSyntheticTrialRegistry());
// Now that field trials have been created, initializes metrics recording.
metrics->InitializeMetricsRecordingState();
@ -1897,6 +1900,11 @@ void ChromeBrowserMainParts::PostMainMessageLoopRun() {
browser_process_->metrics_service()->Stop();
// BrowserProcessImpl::StartTearDown() makes SyntheticTrialRegistry
// unavailable. Since SyntheticTrialSyncer depends on SyntheticTrialRegistry,
// destroy before the tear-down.
synthetic_trial_syncer_.reset();
restart_last_session_ = browser_shutdown::ShutdownPreThreadsStop();
browser_process_->StartTearDown();
#endif // BUILDFLAG(IS_ANDROID)

@ -36,6 +36,10 @@ class CommandLine;
class RunLoop;
}
namespace content {
class SyntheticTrialSyncer;
}
namespace tracing {
class TraceEventSystemStatsMonitor;
}
@ -175,6 +179,8 @@ class ChromeBrowserMainParts : public content::BrowserMainParts {
std::unique_ptr<tracing::TraceEventSystemStatsMonitor>
trace_event_system_stats_monitor_;
std::unique_ptr<content::SyntheticTrialSyncer> synthetic_trial_syncer_;
// Members initialized after / released before main_message_loop_ ------------
std::unique_ptr<BrowserProcessImpl> browser_process_;

@ -18,6 +18,10 @@ namespace metrics {
class MetricsServiceAccessor;
} // namespace metrics
namespace content {
class SyntheticTrialSyncer;
} // namespace content
namespace tpcd::experiment {
class ExperimentManagerImplBrowserTest;
} // namespace tpcd::experiment
@ -87,6 +91,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialRegistry {
friend FieldTrialsProviderTest;
friend SyntheticTrialRegistryTest;
friend ::tpcd::experiment::ExperimentManagerImplBrowserTest;
friend content::SyntheticTrialSyncer;
FRIEND_TEST_ALL_PREFIXES(SyntheticTrialRegistryTest, RegisterSyntheticTrial);
FRIEND_TEST_ALL_PREFIXES(SyntheticTrialRegistryTest,
GetSyntheticFieldTrialsOlderThanSuffix);
@ -132,6 +137,12 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialRegistry {
std::vector<ActiveGroupId>* synthetic_trials,
base::StringPiece suffix = "") const;
// SyntheticTrialSyncer needs to know all current synthetic trial
// groups after launching new child processes.
const std::vector<SyntheticTrialGroup>& GetSyntheticTrialGroups() const {
return synthetic_trial_groups_;
}
// Notifies observers on a synthetic trial list change.
void NotifySyntheticTrialObservers(
const std::vector<SyntheticTrialGroup>& trials_updated,

@ -59,10 +59,6 @@ crash_reporter::CrashKeyString<kVariationsKeySize> g_variations_crash_key(
crash_reporter::CrashKeyString<64> g_variations_seed_version_crash_key(
kVariationsSeedVersionKey);
std::string ActiveGroupToString(const ActiveGroupId& active_group) {
return base::StringPrintf("%x-%x,", active_group.name, active_group.group);
}
std::string GetVariationsSeedVersion() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
// kVariationsSeedVersion should be set by the browser process in
@ -300,4 +296,9 @@ ExperimentListInfo GetExperimentListInfo() {
DCHECK(g_variations_crash_keys);
return g_variations_crash_keys->GetExperimentListInfo();
}
std::string ActiveGroupToString(const ActiveGroupId& active_group) {
return base::StringPrintf("%x-%x,", active_group.name, active_group.group);
}
} // namespace variations

@ -13,6 +13,7 @@
namespace variations {
class SyntheticTrialGroup;
struct ActiveGroupId;
// The key used in crash reports to indicate the number of active experiments.
// Should match the number of entries in kExperimentListKey.
@ -55,6 +56,10 @@ struct COMPONENT_EXPORT(VARIATIONS) ExperimentListInfo {
// in that list in |num_experiments|. Must be called on the UI thread.
COMPONENT_EXPORT(VARIATIONS) ExperimentListInfo GetExperimentListInfo();
// Gets the hash code of the experiment.
COMPONENT_EXPORT(VARIATIONS)
std::string ActiveGroupToString(const ActiveGroupId& active_group);
} // namespace variations
#endif // COMPONENTS_VARIATIONS_VARIATIONS_CRASH_KEYS_H_

@ -2195,6 +2195,7 @@ source_set("browser") {
"storage_partition_impl.h",
"storage_partition_impl_map.cc",
"storage_partition_impl_map.h",
"synthetic_trial_syncer.cc",
"theme_helper.cc",
"theme_helper.h",
"tracing/background_startup_tracing_observer.cc",

@ -0,0 +1,188 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/public/browser/synthetic_trial_syncer.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "components/variations/synthetic_trial_registry.h"
#include "content/common/synthetic_trial_configuration.mojom.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/render_process_host.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace content {
namespace {
std::vector<mojom::SyntheticTrialGroupPtr> ConvertTrialGroupsToMojo(
const std::vector<variations::SyntheticTrialGroup>& trials) {
std::vector<mojom::SyntheticTrialGroupPtr> groups;
for (const auto& trial : trials) {
std::string trial_name(trial.trial_name());
std::string group_name(trial.group_name());
groups.push_back(mojom::SyntheticTrialGroup::New(trial_name, group_name));
}
return groups;
}
void NotifyChildProcess(
mojo::Remote<mojom::SyntheticTrialConfiguration>&
synthetic_trial_configuration,
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
const std::vector<variations::SyntheticTrialGroup>& trials_removed) {
synthetic_trial_configuration->AddOrUpdateSyntheticTrialGroups(
ConvertTrialGroupsToMojo(trials_updated));
synthetic_trial_configuration->RemoveSyntheticTrialGroups(
ConvertTrialGroupsToMojo(trials_removed));
}
class RenderProcessIterator {
public:
using HostType = RenderProcessHost;
RenderProcessIterator() : iter_(RenderProcessHost::AllHostsIterator()) {}
bool IsAtEnd() { return iter_.IsAtEnd(); }
void Advance() { iter_.Advance(); }
const base::Process& GetProcess() {
return iter_.GetCurrentValue()->GetProcess();
}
HostType* GetHost() { return iter_.GetCurrentValue(); }
private:
RenderProcessHost::iterator iter_;
};
class NonRenderProcessIterator {
public:
using HostType = ChildProcessHost;
NonRenderProcessIterator() = default;
bool IsAtEnd() { return iter_.Done(); }
void Advance() { ++iter_; }
const base::Process& GetProcess() { return iter_.GetData().GetProcess(); }
HostType* GetHost() { return iter_.GetHost(); }
private:
BrowserChildProcessHostIterator iter_;
};
template <typename Iterator>
void NotifySyntheticTrialsChange(
base::ProcessId process_id,
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
const std::vector<variations::SyntheticTrialGroup>& trials_removed) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::ProcessId current_pid = base::Process::Current().Pid();
for (Iterator iter; !iter.IsAtEnd(); iter.Advance()) {
const base::Process& process = iter.GetProcess();
if (!process.IsValid() ||
(process_id != base::kNullProcessId && process_id != process.Pid())) {
continue;
}
// Skip if in-browser process mode, because the browser process
// manages synthetic trial groups properly.
if (process.Pid() == current_pid) {
continue;
}
mojo::Remote<mojom::SyntheticTrialConfiguration>
synthetic_trial_configuration;
iter.GetHost()->BindReceiver(
synthetic_trial_configuration.BindNewPipeAndPassReceiver());
NotifyChildProcess(synthetic_trial_configuration, trials_updated,
trials_removed);
}
}
} // namespace
std::unique_ptr<SyntheticTrialSyncer> SyntheticTrialSyncer::Create(
variations::SyntheticTrialRegistry* registry) {
// Only 1 instance is allowed for the browser process.
static bool s_called = false;
CHECK(!s_called);
std::unique_ptr<SyntheticTrialSyncer> instance =
std::make_unique<SyntheticTrialSyncer>(registry);
registry->AddObserver(instance.get());
BrowserChildProcessObserver::Add(instance.get());
s_called = true;
return instance;
}
SyntheticTrialSyncer::SyntheticTrialSyncer(
variations::SyntheticTrialRegistry* registry)
: registry_(registry) {}
SyntheticTrialSyncer::~SyntheticTrialSyncer() {
registry_->RemoveObserver(this);
BrowserChildProcessObserver::Remove(this);
for (RenderProcessIterator it; !it.IsAtEnd(); it.Advance()) {
if (it.GetHost()) {
it.GetHost()->RemoveObserver(this);
}
}
}
void SyntheticTrialSyncer::OnSyntheticTrialsChanged(
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
const std::vector<variations::SyntheticTrialGroup>& trials_removed,
const std::vector<variations::SyntheticTrialGroup>& groups) {
NotifySyntheticTrialsChange<RenderProcessIterator>(
base::kNullProcessId, trials_updated, trials_removed);
NotifySyntheticTrialsChange<NonRenderProcessIterator>(
base::kNullProcessId, trials_updated, trials_removed);
}
void SyntheticTrialSyncer::BrowserChildProcessLaunchedAndConnected(
const ChildProcessData& data) {
if (!data.GetProcess().IsValid()) {
return;
}
NotifySyntheticTrialsChange<NonRenderProcessIterator>(
data.GetProcess().Pid(), registry_->GetSyntheticTrialGroups(), {});
}
void SyntheticTrialSyncer::OnRenderProcessHostCreated(RenderProcessHost* host) {
host->AddObserver(this);
}
void SyntheticTrialSyncer::RenderProcessReady(RenderProcessHost* host) {
const base::Process& process = host->GetProcess();
if (!process.IsValid()) {
return;
}
NotifySyntheticTrialsChange<RenderProcessIterator>(
process.Pid(), registry_->GetSyntheticTrialGroups(), {});
}
void SyntheticTrialSyncer::RenderProcessExited(
RenderProcessHost* host,
const ChildProcessTerminationInfo& info) {
host->RemoveObserver(this);
}
void SyntheticTrialSyncer::RenderProcessHostDestroyed(RenderProcessHost* host) {
// To ensure this is removed from the observer list, call RemoveObserver()
// again.
host->RemoveObserver(this);
}
} // namespace content

@ -45,6 +45,8 @@ target(link_target_type, "child") {
"child_histogram_fetcher_impl.h",
"child_process.cc",
"child_process.h",
"child_process_synthetic_trial_syncer.cc",
"child_process_synthetic_trial_syncer.h",
"child_thread_impl.cc",
"child_thread_impl.h",
"field_trial.cc",

@ -7,6 +7,7 @@
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "content/child/child_histogram_fetcher_impl.h"
#include "content/child/child_process_synthetic_trial_syncer.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "services/tracing/public/cpp/traced_process.h"
@ -16,6 +17,7 @@ namespace content {
void ExposeChildInterfacesToBrowser(
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
const bool in_browser_process,
mojo::BinderMap* binders) {
binders->Add<mojom::ChildHistogramFetcherFactory>(
base::BindRepeating(&ChildHistogramFetcherFactoryImpl::Create),
@ -24,6 +26,12 @@ void ExposeChildInterfacesToBrowser(
base::BindRepeating(&tracing::TracedProcess::OnTracedProcessRequest),
base::SequencedTaskRunner::GetCurrentDefault());
if (!in_browser_process) {
binders->Add<mojom::SyntheticTrialConfiguration>(
base::BindRepeating(&ChildProcessSyntheticTrialSyncer::Create),
base::SequencedTaskRunner::GetCurrentDefault());
}
GetContentClient()->ExposeInterfacesToBrowser(io_task_runner, binders);
}

@ -21,8 +21,17 @@ namespace content {
// process from all child processes (including renderers, GPU, service
// processes, etc.). Interfaces exposed here can be acquired in the browser via
// |RenderProcessHost::BindReceiver()| or |ChildProcessHost::BindReceiver()|.
//
// |in_browser_process| is true if the child process is running in the browser
// process. For example, single-process mode, or in-process gpu mode (forced
// by low-end device mode) makes all child processes or gpu process run in
// the browser process. If the services depend on whether the child process
// is running in the browser process or not (e.g. if using a process-wide
// global variables and |in_browser_process| is true, the browser process and
// the child process will use the same global variables.
void ExposeChildInterfacesToBrowser(
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
bool in_browser_process,
mojo::BinderMap* binders);
} // namespace content

@ -0,0 +1,70 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/child/child_process_synthetic_trial_syncer.h"
#include "base/no_destructor.h"
#include "components/variations/variations_crash_keys.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
namespace content {
ChildProcessSyntheticTrialSyncer::ChildProcessSyntheticTrialSyncer() = default;
ChildProcessSyntheticTrialSyncer::~ChildProcessSyntheticTrialSyncer() = default;
void ChildProcessSyntheticTrialSyncer::Create(
mojo::PendingReceiver<mojom::SyntheticTrialConfiguration> receiver) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<ChildProcessSyntheticTrialSyncer>(),
std::move(receiver));
}
std::unique_ptr<ChildProcessSyntheticTrialSyncer>
ChildProcessSyntheticTrialSyncer::CreateInstanceForTesting() {
return std::make_unique<ChildProcessSyntheticTrialSyncer>();
}
void ChildProcessSyntheticTrialSyncer::AddOrUpdateSyntheticTrialGroups(
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) {
for (const auto& it : trial_groups) {
AddOrUpdateTrialGroupInternal(it->trial_name, it->group_name);
}
variations::UpdateCrashKeysWithSyntheticTrials(trials_);
}
void ChildProcessSyntheticTrialSyncer::RemoveSyntheticTrialGroups(
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) {
std::vector<variations::SyntheticTrialGroup> new_trials;
for (auto& trial : trials_) {
auto find_it =
std::find_if(trial_groups.begin(), trial_groups.end(),
[&trial](mojom::SyntheticTrialGroupPtr& ptr) {
return ptr->trial_name == trial.trial_name() &&
ptr->group_name == trial.group_name();
});
if (find_it != trial_groups.end()) {
continue;
}
new_trials.push_back(trial);
}
trials_.swap(new_trials);
variations::UpdateCrashKeysWithSyntheticTrials(trials_);
}
void ChildProcessSyntheticTrialSyncer::AddOrUpdateTrialGroupInternal(
const std::string& trial_name,
const std::string& group_name) {
for (auto& trial : trials_) {
if (trial.trial_name() == trial_name) {
trial.SetGroupName(group_name);
return;
}
}
trials_.emplace_back(trial_name, group_name,
variations::SyntheticTrialAnnotationMode::kCurrentLog);
}
} // namespace content

@ -0,0 +1,61 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_CHILD_CHILD_PROCESS_SYNTHETIC_TRIAL_SYNCER_H_
#define CONTENT_CHILD_CHILD_PROCESS_SYNTHETIC_TRIAL_SYNCER_H_
#include <string>
#include <vector>
#include "components/variations/synthetic_trials.h"
#include "content/common/content_export.h"
#include "content/common/synthetic_trial_configuration.mojom.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace content {
// This class works in child processes and receives synthetic trial groups
// from SyntheticTrialSyncer running in the browser process via mojo.
//
// When receiving any message from SyntheticTrialSyncer, this class updates
// synthetic trial groups and updates crash keys with synthetic trials.
// This makes crash dumps from non-browser processes have synthetic trial
// information.
class CONTENT_EXPORT ChildProcessSyntheticTrialSyncer
: public mojom::SyntheticTrialConfiguration {
public:
static void Create(
mojo::PendingReceiver<mojom::SyntheticTrialConfiguration> receiver);
ChildProcessSyntheticTrialSyncer();
~ChildProcessSyntheticTrialSyncer() override;
ChildProcessSyntheticTrialSyncer(const ChildProcessSyntheticTrialSyncer&) =
delete;
ChildProcessSyntheticTrialSyncer& operator=(
const ChildProcessSyntheticTrialSyncer&) = delete;
ChildProcessSyntheticTrialSyncer(ChildProcessSyntheticTrialSyncer&&) = delete;
private:
friend class ChildProcessSyntheticTrialSyncerTest;
static std::unique_ptr<ChildProcessSyntheticTrialSyncer>
CreateInstanceForTesting();
// mojom::SyntheticTrialConfiguration:
void AddOrUpdateSyntheticTrialGroups(
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) override;
void RemoveSyntheticTrialGroups(
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) override;
void AddOrUpdateTrialGroupInternal(const std::string& trial_name,
const std::string& group_name);
std::vector<variations::SyntheticTrialGroup> trials_;
};
} // namespace content
#endif // CONTENT_CHILD_CHILD_PROCESS_SYNTHETIC_TRIAL_SYNCER_H_

@ -0,0 +1,119 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/child/child_process_synthetic_trial_syncer.h"
#include "base/test/task_environment.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/variations_crash_keys.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
std::vector<mojom::SyntheticTrialGroupPtr> Create(
std::vector<std::pair<std::string, std::string>> trial_names) {
std::vector<mojom::SyntheticTrialGroupPtr> groups;
for (auto& it : trial_names) {
groups.push_back(mojom::SyntheticTrialGroup::New(it.first, it.second));
}
return groups;
}
} // namespace
class ChildProcessSyntheticTrialSyncerTest : public ::testing::Test {
public:
void SetUp() override {
variations::InitCrashKeys();
syncer_ = ChildProcessSyntheticTrialSyncer::CreateInstanceForTesting();
}
void TearDown() override {
syncer_.reset();
variations::ClearCrashKeysInstanceForTesting();
}
void AddOrUpdateSyntheticTrialGroups(
std::vector<std::pair<std::string, std::string>> groups) {
syncer_->AddOrUpdateSyntheticTrialGroups(Create(groups));
}
void RemoveSyntheticTrialGroups(
std::vector<std::pair<std::string, std::string>> groups) {
syncer_->RemoveSyntheticTrialGroups(Create(groups));
}
std::string GetExperimentHash(std::string trial_name,
std::string group_name) {
return variations::ActiveGroupToString(
variations::MakeActiveGroupId(trial_name, group_name));
}
private:
std::unique_ptr<ChildProcessSyntheticTrialSyncer> syncer_;
base::test::TaskEnvironment task_environment_;
};
TEST_F(ChildProcessSyntheticTrialSyncerTest, Basic) {
AddOrUpdateSyntheticTrialGroups({{"A", "G1"}, {"B", "G2"}});
auto info = variations::GetExperimentListInfo();
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2"),
info.experiment_list);
AddOrUpdateSyntheticTrialGroups({{"C", "G3"}});
info = variations::GetExperimentListInfo();
EXPECT_EQ(3, info.num_experiments);
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2") +
GetExperimentHash("C", "G3"),
info.experiment_list);
AddOrUpdateSyntheticTrialGroups({{"A", "G4"}});
info = variations::GetExperimentListInfo();
EXPECT_EQ(3, info.num_experiments);
EXPECT_EQ(GetExperimentHash("A", "G4") + GetExperimentHash("B", "G2") +
GetExperimentHash("C", "G3"),
info.experiment_list);
RemoveSyntheticTrialGroups({{"B", "G2"}});
info = variations::GetExperimentListInfo();
EXPECT_EQ(2, info.num_experiments);
EXPECT_EQ(GetExperimentHash("A", "G4") + GetExperimentHash("C", "G3"),
info.experiment_list);
RemoveSyntheticTrialGroups({{"C", "G3"}, {"A", "G4"}});
info = variations::GetExperimentListInfo();
EXPECT_EQ(0, info.num_experiments);
}
TEST_F(ChildProcessSyntheticTrialSyncerTest, AddSameTrial) {
AddOrUpdateSyntheticTrialGroups({{"A", "G1"}, {"A", "G2"}, {"A", "G3"}});
auto info = variations::GetExperimentListInfo();
EXPECT_EQ(1, info.num_experiments);
EXPECT_EQ(GetExperimentHash("A", "G3"), info.experiment_list);
}
TEST_F(ChildProcessSyntheticTrialSyncerTest, RemoveFromEmpty) {
RemoveSyntheticTrialGroups({{"B", "G2"}, {"C", "G1"}});
auto info = variations::GetExperimentListInfo();
EXPECT_EQ(0, info.num_experiments);
}
TEST_F(ChildProcessSyntheticTrialSyncerTest, RemoveWrongTrialGroup) {
AddOrUpdateSyntheticTrialGroups({{"A", "G1"}, {"B", "G2"}});
auto info = variations::GetExperimentListInfo();
EXPECT_EQ(2, info.num_experiments);
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2"),
info.experiment_list);
RemoveSyntheticTrialGroups({{"B", "G4"}});
EXPECT_EQ(2, info.num_experiments);
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2"),
info.experiment_list);
}
} // namespace content

@ -42,6 +42,7 @@
#include "build/build_config.h"
#include "content/child/browser_exposed_child_interfaces.h"
#include "content/child/child_process.h"
#include "content/child/child_process_synthetic_trial_syncer.h"
#include "content/common/child_process.mojom.h"
#include "content/common/content_constants_internal.h"
#include "content/common/features.h"
@ -876,7 +877,8 @@ void ChildThreadImpl::ExposeInterfacesToBrowser(mojo::BinderMap binders) {
// NOTE: Do not add new binders directly within this method. Instead, modify
// the definition of |ExposeChildInterfacesToBrowser()|, ensuring security
// review coverage.
ExposeChildInterfacesToBrowser(GetIOTaskRunner(), &binders);
ExposeChildInterfacesToBrowser(GetIOTaskRunner(), IsInBrowserProcess(),
&binders);
ChildThreadImpl::GetIOTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&IOThreadState::ExposeInterfacesToBrowser,

@ -19,6 +19,7 @@
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "components/variations/child_process_field_trial_syncer.h"
#include "content/child/child_process_synthetic_trial_syncer.h"
#include "content/common/associated_interfaces.mojom.h"
#include "content/common/child_process.mojom.h"
#include "content/public/child/child_thread.h"

@ -483,6 +483,7 @@ mojom("mojo_bindings") {
"renderer.mojom",
"renderer_host.mojom",
"renderer_variations_configuration.mojom",
"synthetic_trial_configuration.mojom",
"web_ui.mojom",
]

@ -0,0 +1,21 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module content.mojom;
struct SyntheticTrialGroup {
string trial_name;
string group_name;
};
// Interface used by the browser process to tell the child processes about
// the current active synthetic trial groups.
interface SyntheticTrialConfiguration {
// Tells the child process to add new synthetic trial groups or update the
// existing synthetic trial groups.
AddOrUpdateSyntheticTrialGroups(array<SyntheticTrialGroup> groups);
// Tells the child process to remove the existing synthetic trial groups.
RemoveSyntheticTrialGroups(array<SyntheticTrialGroup> groups);
};

@ -418,6 +418,7 @@ source_set("browser_sources") {
"supported_delegations.cc",
"supported_delegations.h",
"swap_metrics_driver.h",
"synthetic_trial_syncer.h",
"touch_selection_controller_client_manager.h",
"tracing_controller.h",
"tracing_delegate.cc",
@ -544,6 +545,7 @@ source_set("browser_sources") {
"//build:chromeos_buildflags",
"//cc",
"//components/services/storage/public/cpp",
"//components/variations:variations",
"//components/viz/host",
"//content/browser", # Must not be public_deps!
"//device/fido",

@ -0,0 +1,74 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_PUBLIC_BROWSER_SYNTHETIC_TRIAL_SYNCER_H_
#define CONTENT_PUBLIC_BROWSER_SYNTHETIC_TRIAL_SYNCER_H_
#include "base/memory/raw_ptr.h"
#include "components/variations/synthetic_trials.h"
#include "content/common/content_export.h"
#include "content/public/browser/browser_child_process_observer.h"
#include "content/public/browser/render_process_host_creation_observer.h"
#include "content/public/browser/render_process_host_observer.h"
namespace variations {
class SyntheticTrialRegistry;
} // namespace variations
namespace content {
// This class is used by the browser process to tell child processes
// what synthetic trial groups the browser process joins in.
//
// This class registers itself as an observer of SyntheticTrialObserver.
// SyntheticTrialRegistry notifies this class when a synthetic trial group
// is updated.
//
// This class also registers itself as BrowserChildProcessObserver,
// RenderProcessHostCreationObserver and RenderProcessHostObserver to
// tell the synthetic trial groups just after a child process is created.
// At that time, this class gets all joined synthetic groups by calling
// SyntheticTrialRegistry::GetSyntheticTrialGroups().
class CONTENT_EXPORT SyntheticTrialSyncer
: public variations::SyntheticTrialObserver,
public BrowserChildProcessObserver,
public RenderProcessHostCreationObserver,
public RenderProcessHostObserver {
public:
static std::unique_ptr<SyntheticTrialSyncer> Create(
variations::SyntheticTrialRegistry* registry);
explicit SyntheticTrialSyncer(variations::SyntheticTrialRegistry* registry);
~SyntheticTrialSyncer() override;
SyntheticTrialSyncer(const SyntheticTrialSyncer&) = delete;
SyntheticTrialSyncer(SyntheticTrialSyncer&&) = delete;
SyntheticTrialSyncer& operator=(const SyntheticTrialSyncer&) = delete;
private:
// variations::SyntheticTrialObserver:
void OnSyntheticTrialsChanged(
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
const std::vector<variations::SyntheticTrialGroup>& trials_removed,
const std::vector<variations::SyntheticTrialGroup>& groups) override;
// BrowserChildProcessObserver:
void BrowserChildProcessLaunchedAndConnected(
const ChildProcessData& data) override;
// RenderProcessHostCreationObserver:
void OnRenderProcessHostCreated(RenderProcessHost* host) override;
// RenderProcessHostObserver:
void RenderProcessReady(RenderProcessHost* host) override;
void RenderProcessHostDestroyed(RenderProcessHost* host) override;
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override;
const raw_ptr<variations::SyntheticTrialRegistry> registry_;
};
} // namespace content
#endif // CONTENT_PUBLIC_BROWSER_SYNTHETIC_TRIAL_SYNCER_H_

@ -2765,6 +2765,7 @@ test("content_unittests") {
"../browser/worker_host/worker_script_loader_factory_unittest.cc",
"../browser/xr/metrics/session_tracker_unittest.cc",
"../child/blink_platform_impl_unittest.cc",
"../child/child_process_synthetic_trial_syncer_unittest.cc",
"../common/background_fetch/background_fetch_mojom_traits_unittest.cc",
"../common/color_parser_unittest.cc",
"../common/common_param_traits_unittest.cc",
@ -2944,6 +2945,7 @@ test("content_unittests") {
"//components/system_media_controls:test_support",
"//components/ukm:test_support",
"//components/user_prefs/test:test_support",
"//components/variations:variations",
"//components/viz/client",
"//components/viz/common",
"//components/viz/host",