field_trial_util.cc 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/variations/field_trial_config/field_trial_util.h"
  5. #include <stddef.h>
  6. #include <map>
  7. #include <set>
  8. #include <string>
  9. #include <utility>
  10. #include <vector>
  11. #include "base/command_line.h"
  12. #include "base/feature_list.h"
  13. #include "base/metrics/field_trial.h"
  14. #include "base/metrics/field_trial_params.h"
  15. #include "base/strings/escape.h"
  16. #include "base/strings/utf_string_conversions.h"
  17. #include "base/system/sys_info.h"
  18. #include "components/variations/client_filterable_state.h"
  19. #include "components/variations/field_trial_config/fieldtrial_testing_config.h"
  20. #include "components/variations/variations_seed_processor.h"
  21. #include "third_party/abseil-cpp/absl/types/optional.h"
  22. namespace variations {
  23. namespace {
  24. bool HasPlatform(const FieldTrialTestingExperiment& experiment,
  25. Study::Platform platform) {
  26. for (size_t i = 0; i < experiment.platforms_size; ++i) {
  27. if (experiment.platforms[i] == platform)
  28. return true;
  29. }
  30. return false;
  31. }
  32. // Returns true if the experiment config has a different value for
  33. // is_low_end_device than the current system value does.
  34. // If experiment has is_low_end_device missing, then it is False.
  35. bool HasDeviceLevelMismatch(const FieldTrialTestingExperiment& experiment) {
  36. if (!experiment.is_low_end_device.has_value()) {
  37. return false;
  38. }
  39. return experiment.is_low_end_device.value() !=
  40. base::SysInfo::IsLowEndDevice();
  41. }
  42. // Returns true if the experiment config has a missing form_factors or it
  43. // contains the current system's form_factor. Otherwise, it is False.
  44. bool HasFormFactor(const FieldTrialTestingExperiment& experiment,
  45. Study::FormFactor current_form_factor) {
  46. for (size_t i = 0; i < experiment.form_factors_size; ++i) {
  47. if (experiment.form_factors[i] == current_form_factor)
  48. return true;
  49. }
  50. return experiment.form_factors_size == 0;
  51. }
  52. // Returns true if the experiment config has a missing |min_os_version| or
  53. // GetOSVersion() >= |min_os_version|.
  54. bool HasMinOSVersion(const FieldTrialTestingExperiment& experiment) {
  55. if (!experiment.min_os_version)
  56. return true;
  57. return base::Version(experiment.min_os_version) <=
  58. ClientFilterableState::GetOSVersion();
  59. }
  60. // Records the override ui string config. Mainly used for testing.
  61. void ApplyUIStringOverrides(
  62. const FieldTrialTestingExperiment& experiment,
  63. const VariationsSeedProcessor::UIStringOverrideCallback& callback) {
  64. for (size_t i = 0; i < experiment.override_ui_string_size; ++i) {
  65. callback.Run(experiment.override_ui_string[i].name_hash,
  66. base::UTF8ToUTF16(experiment.override_ui_string[i].value));
  67. }
  68. }
  69. // Determines whether an experiment should be skipped or not. An experiment
  70. // should be skipped if it enables or disables a feature that is already
  71. // overridden through the command line.
  72. bool ShouldSkipExperiment(const FieldTrialTestingExperiment& experiment,
  73. base::FeatureList* feature_list) {
  74. for (size_t i = 0; i < experiment.enable_features_size; ++i) {
  75. if (feature_list->IsFeatureOverridden(experiment.enable_features[i])) {
  76. return true;
  77. }
  78. }
  79. for (size_t i = 0; i < experiment.disable_features_size; ++i) {
  80. if (feature_list->IsFeatureOverridden(experiment.disable_features[i])) {
  81. return true;
  82. }
  83. }
  84. return false;
  85. }
  86. void AssociateParamsFromExperiment(
  87. const std::string& study_name,
  88. const FieldTrialTestingExperiment& experiment,
  89. const VariationsSeedProcessor::UIStringOverrideCallback& callback,
  90. base::FeatureList* feature_list) {
  91. if (ShouldSkipExperiment(experiment, feature_list)) {
  92. LOG(WARNING) << "Field trial config study skipped: " << study_name << "."
  93. << experiment.name
  94. << " (some of its features are already overridden)";
  95. return;
  96. }
  97. if (experiment.params_size != 0) {
  98. base::FieldTrialParams params;
  99. for (size_t i = 0; i < experiment.params_size; ++i) {
  100. const FieldTrialTestingExperimentParams& param = experiment.params[i];
  101. params[param.key] = param.value;
  102. }
  103. base::AssociateFieldTrialParams(study_name, experiment.name, params);
  104. }
  105. base::FieldTrial* trial =
  106. base::FieldTrialList::CreateFieldTrial(study_name, experiment.name);
  107. if (!trial) {
  108. LOG(WARNING) << "Field trial config study skipped: " << study_name << "."
  109. << experiment.name
  110. << " (it is overridden from chrome://flags)";
  111. return;
  112. }
  113. for (size_t i = 0; i < experiment.enable_features_size; ++i) {
  114. feature_list->RegisterFieldTrialOverride(
  115. experiment.enable_features[i],
  116. base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
  117. }
  118. for (size_t i = 0; i < experiment.disable_features_size; ++i) {
  119. feature_list->RegisterFieldTrialOverride(
  120. experiment.disable_features[i],
  121. base::FeatureList::OVERRIDE_DISABLE_FEATURE, trial);
  122. }
  123. ApplyUIStringOverrides(experiment, callback);
  124. }
  125. // Choose an experiment to associate. The rules are:
  126. // - Out of the experiments which match this platform:
  127. // - If there is a forcing flag for any experiment, choose the first such
  128. // experiment.
  129. // - Otherwise, If running on low_end_device and the config specify
  130. // a different experiment group for low end devices then pick that.
  131. // - Otherwise, If running on non low_end_device and the config specify
  132. // a different experiment group for non low_end_device then pick that.
  133. // - Otherwise, select the first experiment.
  134. // - The chosen experiment must not enable or disable a feature that is
  135. // explicitly enabled or disabled through a switch, such as the
  136. // |--enable-features| or |--disable-features| switches. If it does, then no
  137. // experiment is associated.
  138. // - If no experiments match this platform, do not associate any of them.
  139. void ChooseExperiment(
  140. const FieldTrialTestingStudy& study,
  141. const VariationsSeedProcessor::UIStringOverrideCallback& callback,
  142. Study::Platform platform,
  143. Study::FormFactor current_form_factor,
  144. base::FeatureList* feature_list) {
  145. const auto& command_line = *base::CommandLine::ForCurrentProcess();
  146. const FieldTrialTestingExperiment* chosen_experiment = nullptr;
  147. for (size_t i = 0; i < study.experiments_size; ++i) {
  148. const FieldTrialTestingExperiment* experiment = study.experiments + i;
  149. if (HasPlatform(*experiment, platform)) {
  150. if (!chosen_experiment && !HasDeviceLevelMismatch(*experiment) &&
  151. HasFormFactor(*experiment, current_form_factor) &&
  152. HasMinOSVersion(*experiment)) {
  153. chosen_experiment = experiment;
  154. }
  155. if (experiment->forcing_flag &&
  156. command_line.HasSwitch(experiment->forcing_flag)) {
  157. chosen_experiment = experiment;
  158. break;
  159. }
  160. }
  161. }
  162. if (chosen_experiment) {
  163. AssociateParamsFromExperiment(study.name, *chosen_experiment, callback,
  164. feature_list);
  165. }
  166. }
  167. } // namespace
  168. std::string EscapeValue(const std::string& value) {
  169. // This needs to be the inverse of UnescapeValue in
  170. // base/metrics/field_trial_params.
  171. std::string net_escaped_str =
  172. base::EscapeQueryParamValue(value, true /* use_plus */);
  173. // net doesn't escape '.' and '*' but base::UnescapeValue() covers those
  174. // cases.
  175. std::string escaped_str;
  176. escaped_str.reserve(net_escaped_str.length());
  177. for (const char ch : net_escaped_str) {
  178. if (ch == '.')
  179. escaped_str.append("%2E");
  180. else if (ch == '*')
  181. escaped_str.append("%2A");
  182. else
  183. escaped_str.push_back(ch);
  184. }
  185. return escaped_str;
  186. }
  187. bool AssociateParamsFromString(const std::string& varations_string) {
  188. return base::AssociateFieldTrialParamsFromString(varations_string,
  189. &base::UnescapeValue);
  190. }
  191. void AssociateParamsFromFieldTrialConfig(
  192. const FieldTrialTestingConfig& config,
  193. const VariationsSeedProcessor::UIStringOverrideCallback& callback,
  194. Study::Platform platform,
  195. Study::FormFactor current_form_factor,
  196. base::FeatureList* feature_list) {
  197. for (size_t i = 0; i < config.studies_size; ++i) {
  198. const FieldTrialTestingStudy& study = config.studies[i];
  199. if (study.experiments_size > 0) {
  200. ChooseExperiment(study, callback, platform, current_form_factor,
  201. feature_list);
  202. } else {
  203. DLOG(ERROR) << "Unexpected empty study: " << study.name;
  204. }
  205. }
  206. }
  207. void AssociateDefaultFieldTrialConfig(
  208. const VariationsSeedProcessor::UIStringOverrideCallback& callback,
  209. Study::Platform platform,
  210. Study::FormFactor current_form_factor,
  211. base::FeatureList* feature_list) {
  212. AssociateParamsFromFieldTrialConfig(kFieldTrialConfig, callback, platform,
  213. current_form_factor, feature_list);
  214. }
  215. } // namespace variations