File: variations_ids_provider.h

package info (click to toggle)
chromium 141.0.7390.107-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,246,132 kB
  • sloc: cpp: 35,264,965; ansic: 7,169,920; javascript: 4,250,185; python: 1,460,635; asm: 950,788; xml: 751,751; pascal: 187,972; sh: 89,459; perl: 88,691; objc: 79,953; sql: 53,924; cs: 44,622; fortran: 24,137; makefile: 22,313; tcl: 15,277; php: 14,018; yacc: 8,995; ruby: 7,553; awk: 3,720; lisp: 3,096; lex: 1,330; ada: 727; jsp: 228; sed: 36
file content (379 lines) | stat: -rw-r--r-- 17,286 bytes parent folder | download | duplicates (4)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_VARIATIONS_VARIATIONS_IDS_PROVIDER_H_
#define COMPONENTS_VARIATIONS_VARIATIONS_IDS_PROVIDER_H_

#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/component_export.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/metrics/field_trial.h"
#include "base/synchronization/lock.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/synthetic_trials.h"
#include "components/variations/variations.mojom.h"
#include "components/variations/variations_associated_data.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"

namespace variations {
namespace internal {

// Minimum and maximum (inclusive) low entropy source values as VariationIDs for
// the X-Client-Data header. This 8,000-value range was reserved in cl/333331461
// (internal CL).
inline constexpr int kLowEntropySourceVariationIdRangeMin = 3320978;
inline constexpr int kLowEntropySourceVariationIdRangeMax = 3328977;
}  // namespace internal

class VariationsClient;

// The key for a VariationsIdsProvider's `variations_headers_map_`. A
// VariationsHeaderKey provides more details about the VariationsIDs included in
// a particular header. For example, the header associated with a key with true
// for `is_signed_in` and Study_GoogleWebVisibility_ANY for `web_visibility` has
// (i) VariationsIDs associated with external experiments, which can be sent
// only for signed-in users and (ii) VariationsIDs that can be sent in first-
// and third-party contexts.
struct COMPONENT_EXPORT(VARIATIONS) VariationsHeaderKey {
  bool is_signed_in;
  Study_GoogleWebVisibility web_visibility;

  // This allows the struct to be used as a key in a map.
  bool operator<(const VariationsHeaderKey& other) const;
};

// A helper class for maintaining client experiments and metrics state
// transmitted in custom HTTP request headers.
// This class is a thread-safe singleton.
class COMPONENT_EXPORT(VARIATIONS) VariationsIdsProvider
    : public base::FieldTrialList::Observer,
      public SyntheticTrialObserver {
 public:
  using ClockFunction = base::RepeatingCallback<base::Time()>;

  class COMPONENT_EXPORT(VARIATIONS) Observer {
   public:
    // Called when variation ids headers are updated.
    virtual void VariationIdsHeaderUpdated() = 0;

   protected:
    virtual ~Observer() = default;
  };

  enum class Mode {
    // Indicates the signed-in parameter supplied to GetClientDataHeaders() is
    // honored.
    kUseSignedInState,

    // Indicates the signed-in parameter supplied to GetClientDataHeaders() is
    // treated as true, regardless of what is supplied. This is intended for
    // embedders (such as WebLayer) that do not have the notion of signed-in.
    kIgnoreSignedInState,

    // Indicates the signed-in parameter supplied to GetClientDataHeaders() is
    // treated as false, regardless of what is supplied.
    kDontSendSignedInVariations,
  };

  // Creates the VariationsIdsProvider instance. This must be called before
  // GetInstance(). Only one instance of VariationsIdsProvider may be created.
  static VariationsIdsProvider* Create(Mode mode);

  static VariationsIdsProvider* GetInstance();

  VariationsIdsProvider(const VariationsIdsProvider&) = delete;
  VariationsIdsProvider& operator=(const VariationsIdsProvider&) = delete;

  Mode mode() const { return mode_; }

  // Sets the clock function to be used for getting the current time.
  // TODO: crbug.com/422445605 - Use this to provide a network time clock.
  void SetClockFunc(ClockFunction clock_func);

  // Returns the X-Client-Data headers corresponding to `is_signed_in`: a header
  // that may be sent in first-party requests and a header that may be sent in
  // third-party requests. For more details, see IsFirstPartyContext() in
  // variations_http_headers.cc.
  //
  // If `is_signed_in` is false, VariationIDs that should be sent for only
  // signed in users (i.e. GOOGLE_WEB_PROPERTIES_SIGNED_IN entries) are not
  // included. Also, computes and caches the header if necessary. `is_signed_in`
  // is impacted by the Mode supplied when VariationsIdsProvider is created.
  // See Mode for details.
  variations::mojom::VariationsHeadersPtr GetClientDataHeaders(
      bool is_signed_in);

  // Returns a space-separated string containing the list of current active
  // variations (as would be reported in the `variation_id` repeated field of
  // the ClientVariations proto). Does not include variation ids that should be
  // sent for signed-in users only and does not include Google app variations.
  // The returned string is guaranteed to have a leading and trailing space,
  // e.g. " 123 234 345 ".
  std::string GetVariationsString();

  // Same as GetVariationString(), but returns Google App variation ids rather
  // than Google Web variations.
  // IMPORTANT: This string is only approved for integrations with the Android
  // Google App and must receive a privacy review before extending to other
  // apps.
  std::string GetGoogleAppVariationsString();

  // Same as GetVariationString(), but returns all triggering IDs.
  // IMPORTANT: This string should only be used for debugging and diagnostics.
  std::string GetTriggerVariationsString();

  // Returns the collection of VariationIDs associated with `keys`. Each entry
  // in the returned vector is unique.
  std::vector<VariationID> GetVariationsVector(
      const std::set<IDCollectionKey>& keys);

  // Returns the collection of variations ids for all Google Web Properties
  // related keys.
  std::vector<VariationID> GetVariationsVectorForWebPropertiesKeys();

  // Sets low entropy source value that was used for client-side randomization
  // of variations.
  void SetLowEntropySourceValue(std::optional<int> low_entropy_source_value);

  // Result of ForceVariationIds() call.
  enum class ForceIdsResult {
    SUCCESS,
    INVALID_VECTOR_ENTRY,  // Invalid entry in `variation_ids`.
    INVALID_SWITCH_ENTRY,  // Invalid entry in `command_line_variation_ids`.
  };

  // Sets *additional* variation ids and trigger variation ids to be encoded in
  // the X-Client-Data request header. This is intended for development use to
  // force a server side experiment id. `variation_ids` should be a list of
  // strings of numeric experiment ids. Ids explicitly passed in `variation_ids`
  // and those in the comma-separated `command_line_variation_ids` are added.
  ForceIdsResult ForceVariationIds(
      const std::vector<std::string>& variation_ids,
      const std::string& command_line_variation_ids);

  // Ensures that the given variation ids and trigger variation ids are not
  // encoded in the X-Client-Data request header. This is intended for
  // development use to force that a server side experiment id is not set.
  // `command_line_variation_ids` are comma-separted experiment ids.
  // Returns true on success.
  bool ForceDisableVariationIds(const std::string& command_line_variation_ids);

  // Methods to register or remove observers of variation ids header update.
  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Resets any cached state for tests.
  void ResetForTesting();

 private:
  using VariationIDEntry = std::pair<VariationID, IDCollectionKey>;
  using VariationIDEntrySet = absl::flat_hash_set<VariationIDEntry>;

  friend class ScopedVariationsIdsProvider;

  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, ForceVariationIds_Valid);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           ForceVariationIds_ValidCommandLine);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           ForceVariationIds_Invalid);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           ForceDisableVariationIds_ValidCommandLine);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           ForceDisableVariationIds_Invalid);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           OnFieldTrialGroupFinalized);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           LowEntropySourceValue_Valid);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           LowEntropySourceValue_Null);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           GetGoogleAppVariationsString);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, GetVariationsString);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, GetVariationsVector);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           GetTimeboxedVariationsVector);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
                           GetVariationsVectorForWebPropertiesKeys);
  FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, GetVariationsVectorImpl);

  explicit VariationsIdsProvider(Mode mode);
  ~VariationsIdsProvider() override;

  static void CreateInstanceForTesting(Mode mode);
  static void DestroyInstanceForTesting();

  // Returns a space-separated string containing the list of current active
  // variations (as would be reported in the `variation_id` repeated field of
  // the ClientVariations proto) for a given ID collection.
  std::string GetVariationsString(const std::set<IDCollectionKey>& keys);

  // base::FieldTrialList::Observer:
  // This will add the variation ID associated with `trial_name` and
  // `group_name` to the variation ID cache.
  void OnFieldTrialGroupFinalized(const base::FieldTrial& trial,
                                  const std::string& group_name) override;

  // metrics::SyntheticTrialObserver:
  void OnSyntheticTrialsChanged(
      const std::vector<SyntheticTrialGroup>& trials_updated,
      const std::vector<SyntheticTrialGroup>& trials_removed,
      const std::vector<SyntheticTrialGroup>& groups) override;

  // Updates `active_variation_ids_set_` and `variations_headers_map_` if
  // necessary.
  void MaybeUpdateVariationIDsAndHeaders() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Helpers to manage the variation ids in `active_variation_ids_set_`. These
  // are expected to be called from `MaybeUpdateVariationIDsAndHeaders()`.
  //
  // Start of helpers for `MaybeUpdateVariationIDsAndHeaders()` {

  // Adds the timeboxed variation ids associated with currently active field
  // trials to`active_variation_ids_set_`.
  void AddActiveVariationIds(base::Time current_time)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Adds the low entropy source value to `active_variation_ids_set_` if a
  // low entropy source value has been set.
  void AddLowEntropySourceValue() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Adds the force-enabled variation ids to `active_variation_ids_set_`.
  void AddForceEnabledVariationIds() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Adds the synthetic variation ids to `active_variation_ids_set_`.
  void AddSyntheticVariationIds() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Removes the force-disabled variation ids from `active_variation_ids_set_`.
  void RemoveForceDisabledVariationIds() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Generates a base64-encoded ClientVariations proto to be used as a header
  // value for the given `is_signed_in` and `context` state. The result is
  // computed based on the values in `active_variation_ids_set_`, so this method
  // is expected to be called from `MaybeUpdateVariationIDsAndHeaders()` after
  // it has updated `active_variation_ids_set_`, to recalculate the cached
  // header values.
  std::string GenerateBase64EncodedProto(
      bool is_signed_in,
      Study_GoogleWebVisibility context) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // } - End of helpers for `MaybeUpdateVariationIDsAndHeaders`.

  // Adds variation ids and trigger variation ids to `target_set`. If
  // `should_dedupe` is true, the ids in `variation_ids` that have already been
  // added as non-Google-app ids are not added to `target_set`. Returns false if
  // any variation ids are malformed or duplicated. Returns true otherwise.
  bool AddVariationIdsToSet(const std::vector<std::string>& variation_ids,
                            bool should_dedupe,
                            VariationIDEntrySet* target_set) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Parses a comma-separated string of variation ids and trigger variation ids
  // and adds them to `target_set`. If `should_dedupe` is true, ids that have
  // already been added as non-Google-app ids are not added to `target_set`.
  // Returns false if any variation ids are malformed or duplicated. Returns
  // true otherwise.
  bool ParseVariationIdsParameter(const std::string& command_line_variation_ids,
                                  bool should_dedupe,
                                  VariationIDEntrySet* target_set)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Returns the value of the X-Client-Data header corresponding to
  // `is_signed_in` and `web_visibility`. Considering `web_visibility` may allow
  // fewer VariationIDs to be sent in third-party contexts.
  std::string GetClientDataHeader(bool is_signed_in,
                                  Study_GoogleWebVisibility web_visibility)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Returns the collection of variation ids matching any of the given
  // `keys`. Each entry in the returned vector will be unique.
  std::vector<VariationID> GetVariationsVectorImpl(
      const std::set<IDCollectionKey>& key);

  // Returns whether `id` has already been added to the active set of variation
  // ids. This includes ids from field trials, synthetic trials, and forced ids.
  // Note that Google app ids are treated differently. They may be reused as a
  // Google Web id.
  bool IsDuplicateId(VariationID id) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Returns the current time..
  base::Time GetCurrentTime() const EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Sets the last update time to the given time.
  void SetLastUpdateTime(base::Time time) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Resets the last update time to base::Time::Min().
  void ResetLastUpdateTime() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Sets the next update time to the given time.
  void SetNextUpdateTime(base::Time time) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Returns true if the cached variations data (`active_variations_ids_set_`,
  // and `variations_headers_map_`) are out of date and need to be recomputed.
  // See the comment in the .cc file for more details about when an update is
  // needed.
  bool UpdateIsNeeded(base::Time current_time) const
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  const Mode mode_;

  // Guards access to variables below.
  base::Lock lock_;

  // Low entropy source value from client that was used for client-side
  // randomization of variations.
  std::optional<int> low_entropy_source_value_ GUARDED_BY(lock_);

  // The last time the caches were updated.
  base::Time last_update_time_ GUARDED_BY(lock_) = base::Time::Min();

  // The (prospective) next time the caches will need to be updated.
  base::Time next_update_time_ GUARDED_BY(lock_) = base::Time::Min();

  // A cache of the currently active variations ids. We keep this so that we
  // don't have to recompute it on every call to GetVariationsVector().
  VariationIDEntrySet active_variation_ids_set_ GUARDED_BY(lock_);

  // Provides the google experiment ids that are force-enabled through
  // ForceVariationIds().
  VariationIDEntrySet force_enabled_ids_set_ GUARDED_BY(lock_);

  // Variations ids from synthetic field trials.
  VariationIDEntrySet synthetic_variation_ids_set_ GUARDED_BY(lock_);

  // Provides the google experiment ids that are force-disabled by command line.
  VariationIDEntrySet force_disabled_ids_set_ GUARDED_BY(lock_);

  // A cache of variations header values. Each header is a base64-encoded
  // ClientVariations proto containing VariationIDs that may be sent to Google
  // web properties. For more details about when this may be sent, see
  // AppendHeaderIfNeeded() in variations_http_headers.cc.
  //
  // The key for each header describes the VariationIDs included in its
  // associated header. See VariationsHeaderKey's comments for more information.
  std::map<VariationsHeaderKey, std::string> variations_headers_map_
      GUARDED_BY(lock_);

  // List of observers to notify on variation ids header update.
  // NOTE this should really check observers are unregistered but due to
  // https://crbug.com/1051937 this isn't currently possible. Note that
  // ObserverList is sequence checked so we can't use that here.
  std::vector<Observer*> observer_list_ GUARDED_BY(lock_);

  // Whether this instance is subscribed to the field trial list. This is used
  // to ensure that the instance is only subscribed once, on first use.
  bool is_subscribed_to_field_trial_list_ GUARDED_BY(lock_) = false;

  // The clock function to be used for getting the current time. This is used
  // to provide a network-based clock, as well as for testing.
  ClockFunction clock_func_ GUARDED_BY(lock_);
};

}  // namespace variations

#endif  // COMPONENTS_VARIATIONS_VARIATIONS_IDS_PROVIDER_H_