1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
content / browser / field_trial_synchronizer.cc [blame]
// Copyright 2012 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/browser/field_trial_synchronizer.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_list_including_low_anonymity.h"
#include "base/strings/strcat.h"
#include "base/threading/thread.h"
#include "components/metrics/persistent_system_profile.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/variations_client.h"
#include "content/common/renderer_variations_configuration.mojom.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "ipc/ipc_channel_proxy.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
namespace content {
namespace {
FieldTrialSynchronizer* g_instance = nullptr;
// Notifies all renderer processes about the |group_name| that is finalized for
// the given field trail (|field_trial_name|). This is called on UI thread.
void NotifyAllRenderersOfFieldTrial(const std::string& field_trial_name,
const std::string& group_name,
bool is_low_anonymity,
bool is_overridden) {
// To iterate over RenderProcessHosts, or to send messages to the hosts, we
// need to be on the UI thread.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Low anonymity or overridden field trials must not be written to persistent
// data, otherwise they might end up being logged in metrics.
//
// TODO(crbug.com/40263398): split this out into a separate class that
// registers using |FieldTrialList::AddObserver()| (and so doesn't get told
// about low anonymity trials at all).
if (!is_low_anonymity) {
// Note this in the persistent profile as it will take a while for a new
// "complete" profile to be generated.
metrics::GlobalPersistentSystemProfile::GetInstance()->AddFieldTrial(
field_trial_name,
is_overridden ? base::StrCat({group_name, variations::kOverrideSuffix})
: group_name);
}
for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
!it.IsAtEnd(); it.Advance()) {
auto* host = it.GetCurrentValue();
IPC::ChannelProxy* channel = host->GetChannel();
// channel might be null in tests.
if (host->IsInitializedAndNotDead() && channel) {
mojo::AssociatedRemote<mojom::RendererVariationsConfiguration>
renderer_variations_configuration;
channel->GetRemoteAssociatedInterface(&renderer_variations_configuration);
renderer_variations_configuration->SetFieldTrialGroup(field_trial_name,
group_name);
}
}
}
} // namespace
// static
void FieldTrialSynchronizer::CreateInstance() {
// Only 1 instance is allowed per process.
DCHECK(!g_instance);
g_instance = new FieldTrialSynchronizer();
}
FieldTrialSynchronizer::FieldTrialSynchronizer() {
// TODO(crbug.com/40263398): consider whether there is a need to exclude low
// anonymity field trials from non-browser processes (or to plumb through the
// anonymity property for more fine-grained access).
bool success = base::FieldTrialListIncludingLowAnonymity::AddObserver(this);
// Ensure the observer was actually registered.
DCHECK(success);
variations::VariationsIdsProvider::GetInstance()->AddObserver(this);
NotifyAllRenderersOfVariationsHeader();
}
void FieldTrialSynchronizer::OnFieldTrialGroupFinalized(
const base::FieldTrial& trial,
const std::string& group_name) {
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
NotifyAllRenderersOfFieldTrial(trial.trial_name(), group_name,
trial.is_low_anonymity(),
trial.IsOverridden());
} else {
// Note that in some tests, `trial` may not be alive when the posted task is
// called.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&NotifyAllRenderersOfFieldTrial, trial.trial_name(),
group_name, trial.is_low_anonymity(),
trial.IsOverridden()));
}
}
// static
void FieldTrialSynchronizer::NotifyAllRenderersOfVariationsHeader() {
// To iterate over RenderProcessHosts, or to send messages to the hosts, we
// need to be on the UI thread.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
!it.IsAtEnd(); it.Advance()) {
UpdateRendererVariationsHeader(it.GetCurrentValue());
}
}
// static
void FieldTrialSynchronizer::UpdateRendererVariationsHeader(
RenderProcessHost* host) {
if (!host->IsInitializedAndNotDead())
return;
IPC::ChannelProxy* channel = host->GetChannel();
// |channel| might be null in tests.
if (!channel)
return;
variations::VariationsClient* client =
host->GetBrowserContext()->GetVariationsClient();
// |client| might be null in tests.
if (!client || client->IsOffTheRecord())
return;
mojo::AssociatedRemote<mojom::RendererVariationsConfiguration>
renderer_variations_configuration;
channel->GetRemoteAssociatedInterface(&renderer_variations_configuration);
renderer_variations_configuration->SetVariationsHeaders(
client->GetVariationsHeaders());
}
void FieldTrialSynchronizer::VariationIdsHeaderUpdated() {
// PostTask to avoid recursive lock.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&FieldTrialSynchronizer::NotifyAllRenderersOfVariationsHeader));
}
FieldTrialSynchronizer::~FieldTrialSynchronizer() {
NOTREACHED_IN_MIGRATION();
}
} // namespace content