// 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(); } } // namespace content