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_
|