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