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
|
// Copyright 2023 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_SERVICE_VARIATIONS_FIELD_TRIAL_CREATOR_BASE_H_
#define COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_FIELD_TRIAL_CREATOR_BASE_H_
// This file contains a base class for VariationsFieldTrialCreator. Its primary
// goal is to minimize generated code size, even at the expense of
// functionality, and to be usable in ChromeOS early boot as part of a
// standalone executable.
// In particular, it will *not*:
// 1) determine locale (this requires linking in libicu, which is several
// hundred KiB). Instead, it will accept locale as a parameter to the
// constructor.
// 2) modify cached UI strings (we remove the ui::ResourceBundle dep entirely)
//
// All such functionality must be implemented by subclasses (e.g.
// VariationsFieldTrialCreator)
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/time/time.h"
#include "base/version_info/channel.h"
#include "build/build_config.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/metrics.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/seed_response.h"
#include "components/variations/service/buildflags.h"
#include "components/variations/service/safe_seed_manager.h"
#include "components/variations/service/variations_service_client.h"
#include "components/variations/variations_seed_store.h"
#include "components/version_info/channel.h"
namespace metrics {
class MetricsStateManager;
}
namespace variations {
class EntropyProviders;
// TODO(crbug.com/424154785): Clean this up if low entropy source values are no
// longer transmitted with VariationIDs.
struct CreateTrialsResult {
bool applied_seed = false;
std::optional<bool> seed_has_limited_layer;
};
// Just maps one set of enum values to another. Nothing to see here.
Study::Channel ConvertProductChannelToStudyChannel(
version_info::Channel product_channel);
// Denotes whether Chrome used a variations seed. Also captures (a) the kind of
// seed and (b) the conditions under which the seed was used or failed to be
// used. Exposed for testing.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class SeedUsage {
kRegularSeedUsed = 0,
kExpiredRegularSeedNotUsed = 1,
kUnloadableRegularSeedNotUsed = 2,
kSafeSeedUsed = 3,
kExpiredSafeSeedNotUsed = 4,
// The below three enumerators were deprecated in M100.
// kCorruptedSafeSeedNotUsed = 5,
// kRegularSeedUsedAfterEmptySafeSeedLoaded = 6,
// kExpiredRegularSeedNotUsedAfterEmptySafeSeedLoaded = 7,
// kCorruptedRegularSeedNotUsedAfterEmptySafeSeedLoaded = 8,
kRegularSeedForFutureMilestoneNotUsed = 9,
kSafeSeedForFutureMilestoneNotUsed = 10,
kUnloadableSafeSeedNotUsed = 11,
kNullSeedUsed = 12,
kMaxValue = kNullSeedUsed,
};
// Denotes a variations seed's expiry state. Exposed for testing.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class VariationsSeedExpiry {
kNotExpired = 0,
kFetchTimeMissing = 1,
kExpired = 2,
kMaxValue = kExpired,
};
enum LoadPermanentConsistencyCountryResult {
LOAD_COUNTRY_NO_PREF_NO_SEED = 0,
LOAD_COUNTRY_NO_PREF_HAS_SEED,
LOAD_COUNTRY_INVALID_PREF_NO_SEED,
LOAD_COUNTRY_INVALID_PREF_HAS_SEED,
LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ,
LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ,
LOAD_COUNTRY_HAS_PERMANENT_OVERRIDDEN_COUNTRY,
LOAD_COUNTRY_MAX,
};
class PlatformFieldTrials;
class SafeSeedManagerBase;
class VariationsServiceClient;
// Used to set up field trials based on stored variations seed data.
class VariationsFieldTrialCreatorBase {
public:
// Caller is responsible for ensuring that the VariationsServiceClient
// passed to the constructor stays valid for the lifetime of this object.
//
// |client| provides some platform-specific operations for variations.
// |seed_store| manages seed data.
// |locale_cb| computes the locale, given a PrefService for local_state.
VariationsFieldTrialCreatorBase(
VariationsServiceClient* client,
std::unique_ptr<VariationsSeedStore> seed_store,
base::OnceCallback<std::string(PrefService*)> locale_cb);
VariationsFieldTrialCreatorBase(const VariationsFieldTrialCreatorBase&) =
delete;
VariationsFieldTrialCreatorBase& operator=(
const VariationsFieldTrialCreatorBase&) = delete;
virtual ~VariationsFieldTrialCreatorBase();
// Returns what variations will consider to be the latest country. Returns
// empty if it is not available.
std::string GetLatestCountry() const;
VariationsSeedStore* seed_store() { return seed_store_.get(); }
// Sets up field trials based on stored variations seed data. Returns whether
// setup completed successfully.
//
// |variation_ids| allows for forcing ids selected in chrome://flags.
// |command_line_variation_ids| allows for forcing ids through the
// "--force-variation-ids" command line flag. It should be a comma-separated
// list of variation ids. Ids prefixed with the character "t" will be treated
// as Trigger Variation Ids.
// |extra_overrides| gives a list of feature overrides that should be applied
// after the features explicitly disabled/enabled from the command line via
// --disable-features and --enable-features, but before field trials.
// |feature_list| contains the list of all active features for this client.
// Must not be null.
// |metrics_state_manager| facilitates signaling that Chrome has not yet
// exited cleanly. Must not be null.
// |platform_field_trials| provides the
// platform-specific field trial setup for Chrome. Must not be null.
// |safe_seed_manager| should be notified of the combined server and client
// state that was activated to create the field trials (only when the return
// value is true). Must not be null.
// |add_entropy_source_to_variations_ids| controls if variations ID for the
// low entropy source should be added to FIRST_PARTY variation headers.
// |entropy_providers| Used to provide entropy to field trials.
// TODO(b/263797385): eliminate this argument if we can always add the ID.
//
// NOTE: The ordering of the FeatureList method calls is such that the
// explicit --disable-features and --enable-features from the command line
// take precedence over |extra_overrides|, which takes precedence over the
// field trials.
bool SetUpFieldTrials(
const std::vector<std::string>& variation_ids,
const std::string& command_line_variation_ids,
const std::vector<base::FeatureList::FeatureOverrideInfo>&
extra_overrides,
std::unique_ptr<base::FeatureList> feature_list,
metrics::MetricsStateManager* metrics_state_manager,
PlatformFieldTrials* platform_field_trials,
SafeSeedManagerBase* safe_seed_manager,
bool add_entropy_source_to_variations_ids,
const EntropyProviders& entropy_providers);
// Returns all of the client state used for filtering studies.
// As a side-effect, may update the stored permanent consistency country.
std::unique_ptr<ClientFilterableState> GetClientFilterableStateForVersion(
const base::Version& version);
// Loads the country code to use for filtering permanent consistency studies,
// updating the stored country code if the stored value was for a different
// Chrome version. The country used for permanent consistency studies is kept
// consistent between Chrome upgrades in order to avoid annoying the user due
// to experiment churn while traveling.
std::string LoadPermanentConsistencyCountry(
const base::Version& version,
const std::string& latest_country);
// Returns the country code used for filtering permanent consistency studies.
// This can only be called after field trials have been initialized or if
// prefs::kVariationsPermanentOverriddenCountry has been overridden through
// StoreVariationsOverriddenCountry() in this session.
std::string GetPermanentConsistencyCountry() const;
// Sets the stored permanent country pref for this client.
void StorePermanentCountry(const base::Version& version,
const std::string& country);
// Sets the stored permanent variations overridden country pref for this
// client.
void StoreVariationsOverriddenCountry(const std::string& country);
// Allow the platform that is used to filter the set of active trials to be
// overridden.
void OverrideVariationsPlatform(Study::Platform platform_override);
// Gets the last fetch time of the seed. If using the safe seed, returns
// the safe seed fetch time. Otherwise, returns the last fetch time of the
// latest seed. Returns base::Time() if there is no seed.
base::Time GetSeedFetchTime();
// Returns the locale that was used for evaluating trials.
const std::string& application_locale() const { return application_locale_; }
SeedType seed_type() const { return seed_type_; }
// Overrides cached UI strings on the resource bundle once it is initialized.
// To be implemented by subclasses, if they have need for UI strings.
virtual void OverrideCachedUIStrings() = 0;
// Returns whether the map of the cached UI strings to override is empty.
// To be implemented by subclasses, if they have need for UI strings.
virtual bool IsOverrideResourceMapEmpty() = 0;
// Returns the client-side time when the seed was last fetched. Returns
// base::Time() if there is no seed.
base::Time GetLatestSeedFetchTime();
protected:
// Get the platform we're running on, respecting OverrideVariationsPlatform().
// Protected for testing.
Study::Platform GetPlatform();
// Get the client's current form factor. Protected for testing.
Study::FormFactor GetCurrentFormFactor();
#if BUILDFLAG(FIELDTRIAL_TESTING_ENABLED)
// Applies the field trial testing config defined in
// testing/variations/fieldtrial_testing_config.json to the current session.
// Protected and virtual for testing.
virtual void ApplyFieldTrialTestingConfig(base::FeatureList* feature_list);
#endif // BUILDFLAG(FIELDTRIAL_TESTING_ENABLED)
// Read the google group memberships from local-state prefs.
// Protected for testing.
base::flat_set<uint64_t> GetGoogleGroupsFromPrefs();
// Overrides the string resource specified by |hash| with |str| in the
// resource bundle. Protected for testing.
// To be implemented by subclasses if they need UI string overrides.
virtual void OverrideUIString(uint32_t hash, const std::u16string& str) = 0;
private:
// Returns true if the loaded VariationsSeed has expired. An expired seed is
// one that (a) was fetched over |kMaxSeedAgeDays| ago and (b) is older than
// the binary build time.
//
// Also, records a couple VariationsSeed-related metrics.
bool HasSeedExpired();
// Returns true if the loaded VariationsSeed is for a future milestone (e.g.
// if the client is on M92 and the seed was fetched with M93). A seed for a
// future milestone is invalid as it may be missing studies filtered out by
// the server.
bool IsSeedForFutureMilestone(bool is_safe_seed);
// Creates field trials from a VariationsSeed. Returns whether the seed was
// successfully applied and whether the seed contains a limited-entropy-mode
// layer.
//
// |entropy_providers| helps to randomize field trial groups.
// |feature_list| associates field trial groups with features.
// |safe_seed_manager| has two responsibilities. It is used to determine which
// seed to apply, if any. If the latest seed is successfully applied, the
// manager also stores the applied variations state.
// |client_state| is used for filtering studies in the seed.
//
// If the seed isn't successfully loaded or if the seed fails some checks
// (e.g. if the seed has expired), then no trials are created from the seed
// and the client uses client-side defaults for features, like
// DISABLED_BY_DEFAULT.
CreateTrialsResult CreateTrialsFromSeed(
const EntropyProviders& entropy_providers,
base::FeatureList* feature_list,
SafeSeedManagerBase* safe_seed_manager,
std::unique_ptr<ClientFilterableState> client_state);
// Reads a seed's data and signature from the file at |json_seed_path| and
// writes them to Local State. Exits Chrome if (A) the file's contents can't
// be loaded or (B) if the contents do not contain |kVariationsCompressedSeed|
// or |kVariationsSeedSignature|. Also forces Chrome to not run in variations
// safe mode. Used for variations seed testing.
void LoadSeedFromJsonFile(const base::FilePath& json_seed_path);
// Returns the seed store. Virtual for testing.
virtual VariationsSeedStore* GetSeedStore();
PrefService* local_state() { return seed_store_->local_state(); }
const PrefService* local_state() const { return seed_store_->local_state(); }
raw_ptr<VariationsServiceClient> client_;
std::unique_ptr<VariationsSeedStore> seed_store_;
// Seed type used for variations.
SeedType seed_type_ = SeedType::kNullSeed;
// Tracks whether |CreateTrialsFromSeed| has been called, to ensure that it is
// called at most once.
bool create_trials_from_seed_called_ = false;
// The application locale won't change after the startup, so we cache the
// value the first time when GetApplicationLocale() is called in the
// constructor.
std::string application_locale_;
// Platform to be used for variations filtering, overriding the current
// platform.
std::optional<Study::Platform> platform_override_;
// Caches the UI strings which need to be overridden in the resource bundle.
// These strings are cached before the resource bundle is initialized.
std::unordered_map<int, std::u16string> overridden_strings_map_;
// Holds the country code to use for filtering permanent consistency studies
std::string permanent_consistency_country_;
// Tracks whether |permanent_consistency_country_| has been initialized to
// ensure it contains a relevant value.
bool permanent_consistency_country_initialized_ = false;
SEQUENCE_CHECKER(sequence_checker_);
};
// A testing feature that forces a crash during field trial creation
// on developer and test builds.
BASE_DECLARE_FEATURE(kForceFieldTrialSetupCrashForTesting);
} // namespace variations
#endif // COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_FIELD_TRIAL_CREATOR_BASE_H_
|