Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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 
      5 #include "chrome/browser/about_flags.h"
      6 
      7 #include <algorithm>
      8 #include <iterator>
      9 #include <map>
     10 #include <set>
     11 
     12 #include "base/command_line.h"
     13 #include "base/memory/singleton.h"
     14 #include "base/string_number_conversions.h"
     15 #include "base/values.h"
     16 #include "chrome/browser/metrics/user_metrics.h"
     17 #include "chrome/browser/prefs/pref_service.h"
     18 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     19 #include "chrome/common/chrome_switches.h"
     20 #include "chrome/common/pref_names.h"
     21 #include "grit/generated_resources.h"
     22 #include "ui/base/l10n/l10n_util.h"
     23 
     24 namespace about_flags {
     25 
     26 // Macros to simplify specifying the type.
     27 #define SINGLE_VALUE_TYPE_AND_VALUE(command_line_switch, switch_value) \
     28     Experiment::SINGLE_VALUE, command_line_switch, switch_value, NULL, 0
     29 #define SINGLE_VALUE_TYPE(command_line_switch) \
     30     SINGLE_VALUE_TYPE_AND_VALUE(command_line_switch, "")
     31 #define MULTI_VALUE_TYPE(choices) \
     32     Experiment::MULTI_VALUE, "", "", choices, arraysize(choices)
     33 
     34 namespace {
     35 
     36 const unsigned kOsAll = kOsMac | kOsWin | kOsLinux | kOsCrOS;
     37 
     38 // Names for former Chrome OS Labs experiments, shared with prefs migration
     39 // code.
     40 const char kMediaPlayerExperimentName[] = "media-player";
     41 const char kAdvancedFileSystemExperimentName[] = "advanced-file-system";
     42 const char kVerticalTabsExperimentName[] = "vertical-tabs";
     43 
     44 // RECORDING USER METRICS FOR FLAGS:
     45 // -----------------------------------------------------------------------------
     46 // The first line of the experiment is the internal name. If you'd like to
     47 // gather statistics about the usage of your flag, you should append a marker
     48 // comment to the end of the feature name, like so:
     49 //   "my-special-feature",  // FLAGS:RECORD_UMA
     50 //
     51 // After doing that, run //chrome/tools/extract_actions.py (see instructions at
     52 // the top of that file for details) to update the chromeactions.txt file, which
     53 // will enable UMA to record your feature flag.
     54 //
     55 // After your feature has shipped under a flag, you can locate the metrics
     56 // under the action name AboutFlags_internal-action-name. Actions are recorded
     57 // once per startup, so you should divide this number by AboutFlags_StartupTick
     58 // to get a sense of usage. Note that this will not be the same as number of
     59 // users with a given feature enabled because users can quit and relaunch
     60 // the application multiple times over a given time interval.
     61 // TODO(rsesek): See if there's a way to count per-user, rather than
     62 // per-startup.
     63 
     64 // To add a new experiment add to the end of kExperiments. There are two
     65 // distinct types of experiments:
     66 // . SINGLE_VALUE: experiment is either on or off. Use the SINGLE_VALUE_TYPE
     67 //   macro for this type supplying the command line to the macro.
     68 // . MULTI_VALUE: a list of choices, the first of which should correspond to a
     69 //   deactivated state for this lab (i.e. no command line option).  To specify
     70 //   this type of experiment use the macro MULTI_VALUE_TYPE supplying it the
     71 //   array of choices.
     72 // See the documentation of Experiment for details on the fields.
     73 //
     74 // When adding a new choice, add it to the end of the list.
     75 const Experiment kExperiments[] = {
     76   {
     77     "expose-for-tabs",  // FLAGS:RECORD_UMA
     78     IDS_FLAGS_TABPOSE_NAME,
     79     IDS_FLAGS_TABPOSE_DESCRIPTION,
     80     kOsMac,
     81 #if defined(OS_MACOSX)
     82     // The switch exists only on OS X.
     83     SINGLE_VALUE_TYPE(switches::kEnableExposeForTabs)
     84 #else
     85     SINGLE_VALUE_TYPE("")
     86 #endif
     87   },
     88   {
     89     "vertical-tabs",  // FLAGS:RECORD_UMA
     90     IDS_FLAGS_SIDE_TABS_NAME,
     91     IDS_FLAGS_SIDE_TABS_DESCRIPTION,
     92     kOsWin | kOsCrOS,
     93     SINGLE_VALUE_TYPE(switches::kEnableVerticalTabs)
     94   },
     95   {
     96     "remoting",  // FLAGS:RECORD_UMA
     97     IDS_FLAGS_REMOTING_NAME,
     98     IDS_FLAGS_REMOTING_DESCRIPTION,
     99     kOsAll,
    100     SINGLE_VALUE_TYPE(switches::kEnableRemoting)
    101   },
    102   {
    103     "conflicting-modules-check",  // FLAGS:RECORD_UMA
    104     IDS_FLAGS_CONFLICTS_CHECK_NAME,
    105     IDS_FLAGS_CONFLICTS_CHECK_DESCRIPTION,
    106     kOsWin,
    107     SINGLE_VALUE_TYPE(switches::kConflictingModulesCheck)
    108   },
    109   {
    110     "cloud-print-proxy",  // FLAGS:RECORD_UMA
    111     IDS_FLAGS_CLOUD_PRINT_PROXY_NAME,
    112     IDS_FLAGS_CLOUD_PRINT_PROXY_DESCRIPTION,
    113 #if defined(GOOGLE_CHROME_BUILD)
    114     // For a Chrome build, we know we have a PDF plug-in on Windows, so it's
    115     // fully enabled. Linux still need some final polish.
    116     kOsLinux,
    117 #else
    118     // Otherwise, where we know Windows could be working if a viable PDF
    119     // plug-in could be supplied, we'll keep the lab enabled. Mac always has
    120     // PDF rasterization available, so no flag needed there.
    121     kOsWin | kOsLinux,
    122 #endif
    123     SINGLE_VALUE_TYPE(switches::kEnableCloudPrintProxy)
    124   },
    125   {
    126     "crxless-web-apps",
    127     IDS_FLAGS_CRXLESS_WEB_APPS_NAME,
    128     IDS_FLAGS_CRXLESS_WEB_APPS_DESCRIPTION,
    129     kOsAll,
    130     SINGLE_VALUE_TYPE(switches::kEnableCrxlessWebApps)
    131   },
    132   {
    133     "composited-layer-borders",
    134     IDS_FLAGS_COMPOSITED_LAYER_BORDERS,
    135     IDS_FLAGS_COMPOSITED_LAYER_BORDERS_DESCRIPTION,
    136     kOsAll,
    137     SINGLE_VALUE_TYPE(switches::kShowCompositedLayerBorders)
    138   },
    139   {
    140     "show-fps-counter",
    141     IDS_FLAGS_SHOW_FPS_COUNTER,
    142     IDS_FLAGS_SHOW_FPS_COUNTER_DESCRIPTION,
    143     kOsAll,
    144     SINGLE_VALUE_TYPE(switches::kShowFPSCounter)
    145   },
    146   {
    147     "gpu-canvas-2d",  // FLAGS:RECORD_UMA
    148     IDS_FLAGS_ACCELERATED_CANVAS_2D_NAME,
    149     IDS_FLAGS_ACCELERATED_CANVAS_2D_DESCRIPTION,
    150     kOsWin | kOsLinux | kOsCrOS,
    151     SINGLE_VALUE_TYPE(switches::kEnableAccelerated2dCanvas)
    152   },
    153   {
    154     "print-preview",  // FLAGS:RECORD_UMA
    155     IDS_FLAGS_PRINT_PREVIEW_NAME,
    156     IDS_FLAGS_PRINT_PREVIEW_DESCRIPTION,
    157     kOsMac | kOsWin | kOsLinux, // This switch is not available in CrOS.
    158     SINGLE_VALUE_TYPE(switches::kEnablePrintPreview)
    159   },
    160   {
    161     "enable-nacl",  // FLAGS:RECORD_UMA
    162     IDS_FLAGS_ENABLE_NACL_NAME,
    163     IDS_FLAGS_ENABLE_NACL_DESCRIPTION,
    164     kOsAll,
    165     SINGLE_VALUE_TYPE(switches::kEnableNaCl)
    166   },
    167   {
    168     "dns-server",  // FLAGS:RECORD_UMA
    169     IDS_FLAGS_DNS_SERVER_NAME,
    170     IDS_FLAGS_DNS_SERVER_DESCRIPTION,
    171     kOsLinux,
    172     SINGLE_VALUE_TYPE(switches::kDnsServer)
    173   },
    174   {
    175     "extension-apis",  // FLAGS:RECORD_UMA
    176     IDS_FLAGS_EXPERIMENTAL_EXTENSION_APIS_NAME,
    177     IDS_FLAGS_EXPERIMENTAL_EXTENSION_APIS_DESCRIPTION,
    178     kOsAll,
    179     SINGLE_VALUE_TYPE(switches::kEnableExperimentalExtensionApis)
    180   },
    181   {
    182     "click-to-play",  // FLAGS:RECORD_UMA
    183     IDS_FLAGS_CLICK_TO_PLAY_NAME,
    184     IDS_FLAGS_CLICK_TO_PLAY_DESCRIPTION,
    185     kOsAll,
    186     SINGLE_VALUE_TYPE(switches::kEnableClickToPlay)
    187   },
    188   {
    189     "disable-hyperlink-auditing",
    190     IDS_FLAGS_DISABLE_HYPERLINK_AUDITING_NAME,
    191     IDS_FLAGS_DISABLE_HYPERLINK_AUDITING_DESCRIPTION,
    192     kOsAll,
    193     SINGLE_VALUE_TYPE(switches::kNoPings)
    194   },
    195   {
    196     "experimental-location-features",  // FLAGS:RECORD_UMA
    197     IDS_FLAGS_EXPERIMENTAL_LOCATION_FEATURES_NAME,
    198     IDS_FLAGS_EXPERIMENTAL_LOCATION_FEATURES_DESCRIPTION,
    199     kOsMac | kOsWin | kOsLinux,  // Currently does nothing on CrOS.
    200     SINGLE_VALUE_TYPE(switches::kExperimentalLocationFeatures)
    201   },
    202   {
    203     "block-reading-third-party-cookies",
    204     IDS_FLAGS_BLOCK_ALL_THIRD_PARTY_COOKIES_NAME,
    205     IDS_FLAGS_BLOCK_ALL_THIRD_PARTY_COOKIES_DESCRIPTION,
    206     kOsAll,
    207     SINGLE_VALUE_TYPE(switches::kBlockReadingThirdPartyCookies)
    208   },
    209   {
    210     "disable-interactive-form-validation",
    211     IDS_FLAGS_DISABLE_INTERACTIVE_FORM_VALIDATION_NAME,
    212     IDS_FLAGS_DISABLE_INTERACTIVE_FORM_VALIDATION_DESCRIPTION,
    213     kOsAll,
    214     SINGLE_VALUE_TYPE(switches::kDisableInteractiveFormValidation)
    215   },
    216   {
    217     "webaudio",
    218     IDS_FLAGS_WEBAUDIO_NAME,
    219     IDS_FLAGS_WEBAUDIO_DESCRIPTION,
    220     kOsMac,  // TODO(crogers): add windows and linux when FFT is ready.
    221     SINGLE_VALUE_TYPE(switches::kEnableWebAudio)
    222   },
    223   {
    224     "p2papi",
    225     IDS_FLAGS_P2P_API_NAME,
    226     IDS_FLAGS_P2P_API_DESCRIPTION,
    227     kOsAll,
    228     SINGLE_VALUE_TYPE(switches::kEnableP2PApi)
    229   },
    230   {
    231     "focus-existing-tab-on-open",  // FLAGS:RECORD_UMA
    232     IDS_FLAGS_FOCUS_EXISTING_TAB_ON_OPEN_NAME,
    233     IDS_FLAGS_FOCUS_EXISTING_TAB_ON_OPEN_DESCRIPTION,
    234     kOsAll,
    235     SINGLE_VALUE_TYPE(switches::kFocusExistingTabOnOpen)
    236   },
    237   {
    238     "new-tab-page-4",
    239     IDS_FLAGS_NEW_TAB_PAGE_4_NAME,
    240     IDS_FLAGS_NEW_TAB_PAGE_4_DESCRIPTION,
    241     kOsAll,
    242     SINGLE_VALUE_TYPE(switches::kNewTabPage4)
    243   },
    244   {
    245     "tab-groups-context-menu",
    246     IDS_FLAGS_TAB_GROUPS_CONTEXT_MENU_NAME,
    247     IDS_FLAGS_TAB_GROUPS_CONTEXT_MENU_DESCRIPTION,
    248     kOsWin,
    249     SINGLE_VALUE_TYPE(switches::kEnableTabGroupsContextMenu)
    250   },
    251   {
    252     "ppapi-flash-in-process",
    253     IDS_FLAGS_PPAPI_FLASH_IN_PROCESS_NAME,
    254     IDS_FLAGS_PPAPI_FLASH_IN_PROCESS_DESCRIPTION,
    255     kOsAll,
    256     SINGLE_VALUE_TYPE(switches::kPpapiFlashInProcess)
    257   },
    258 #if defined(TOOLKIT_GTK)
    259   {
    260     "global-gnome-menu",
    261     IDS_FLAGS_LINUX_GLOBAL_MENUBAR_NAME,
    262     IDS_FLAGS_LINUX_GLOBAL_MENUBAR_DESCRIPTION,
    263     kOsLinux,
    264     SINGLE_VALUE_TYPE(switches::kGlobalGnomeMenu)
    265   },
    266 #endif
    267   {
    268     "enable-experimental-eap",
    269     IDS_FLAGS_ENABLE_EXPERIMENTAL_EAP_NAME,
    270     IDS_FLAGS_ENABLE_EXPERIMENTAL_EAP_DESCRIPTION,
    271     kOsCrOS,
    272 #if defined(OS_CHROMEOS)
    273     // The switch exists only on Chrome OS.
    274     SINGLE_VALUE_TYPE(switches::kEnableExperimentalEap)
    275 #else
    276     SINGLE_VALUE_TYPE("")
    277 #endif
    278   },
    279   {
    280     "enable-vpn",
    281     IDS_FLAGS_ENABLE_VPN_NAME,
    282     IDS_FLAGS_ENABLE_VPN_DESCRIPTION,
    283     kOsCrOS,
    284 #if defined(OS_CHROMEOS)
    285     // The switch exists only on Chrome OS.
    286     SINGLE_VALUE_TYPE(switches::kEnableVPN)
    287 #else
    288     SINGLE_VALUE_TYPE("")
    289 #endif
    290   },
    291   {
    292     "multi-profiles",
    293     IDS_FLAGS_MULTI_PROFILES_NAME,
    294     IDS_FLAGS_MULTI_PROFILES_DESCRIPTION,
    295     kOsAll,
    296     SINGLE_VALUE_TYPE(switches::kMultiProfiles)
    297   },
    298 };
    299 
    300 const Experiment* experiments = kExperiments;
    301 size_t num_experiments = arraysize(kExperiments);
    302 
    303 // Stores and encapsulates the little state that about:flags has.
    304 class FlagsState {
    305  public:
    306   FlagsState() : needs_restart_(false) {}
    307   void ConvertFlagsToSwitches(PrefService* prefs, CommandLine* command_line);
    308   bool IsRestartNeededToCommitChanges();
    309   void SetExperimentEnabled(
    310       PrefService* prefs, const std::string& internal_name, bool enable);
    311   void RemoveFlagsSwitches(
    312       std::map<std::string, CommandLine::StringType>* switch_list);
    313   void reset();
    314 
    315   // Returns the singleton instance of this class
    316   static FlagsState* GetInstance() {
    317     return Singleton<FlagsState>::get();
    318   }
    319 
    320  private:
    321   bool needs_restart_;
    322   std::map<std::string, std::string> flags_switches_;
    323 
    324   DISALLOW_COPY_AND_ASSIGN(FlagsState);
    325 };
    326 
    327 // Extracts the list of enabled lab experiments from preferences and stores them
    328 // in a set.
    329 void GetEnabledFlags(const PrefService* prefs, std::set<std::string>* result) {
    330   const ListValue* enabled_experiments = prefs->GetList(
    331       prefs::kEnabledLabsExperiments);
    332   if (!enabled_experiments)
    333     return;
    334 
    335   for (ListValue::const_iterator it = enabled_experiments->begin();
    336        it != enabled_experiments->end();
    337        ++it) {
    338     std::string experiment_name;
    339     if (!(*it)->GetAsString(&experiment_name)) {
    340       LOG(WARNING) << "Invalid entry in " << prefs::kEnabledLabsExperiments;
    341       continue;
    342     }
    343     result->insert(experiment_name);
    344   }
    345 }
    346 
    347 // Takes a set of enabled lab experiments
    348 void SetEnabledFlags(
    349     PrefService* prefs, const std::set<std::string>& enabled_experiments) {
    350   ListPrefUpdate update(prefs, prefs::kEnabledLabsExperiments);
    351   ListValue* experiments_list = update.Get();
    352 
    353   experiments_list->Clear();
    354   for (std::set<std::string>::const_iterator it = enabled_experiments.begin();
    355        it != enabled_experiments.end();
    356        ++it) {
    357     experiments_list->Append(new StringValue(*it));
    358   }
    359 }
    360 
    361 // Returns the name used in prefs for the choice at the specified index.
    362 std::string NameForChoice(const Experiment& e, int index) {
    363   DCHECK_EQ(Experiment::MULTI_VALUE, e.type);
    364   DCHECK_LT(index, e.num_choices);
    365   return std::string(e.internal_name) + about_flags::testing::kMultiSeparator +
    366       base::IntToString(index);
    367 }
    368 
    369 // Adds the internal names for the specified experiment to |names|.
    370 void AddInternalName(const Experiment& e, std::set<std::string>* names) {
    371   if (e.type == Experiment::SINGLE_VALUE) {
    372     names->insert(e.internal_name);
    373   } else {
    374     DCHECK_EQ(Experiment::MULTI_VALUE, e.type);
    375     for (int i = 0; i < e.num_choices; ++i)
    376       names->insert(NameForChoice(e, i));
    377   }
    378 }
    379 
    380 // Confirms that an experiment is valid, used in a DCHECK in
    381 // SanitizeList below.
    382 bool ValidateExperiment(const Experiment& e) {
    383   switch (e.type) {
    384     case Experiment::SINGLE_VALUE:
    385       DCHECK_EQ(0, e.num_choices);
    386       DCHECK(!e.choices);
    387       break;
    388     case Experiment::MULTI_VALUE:
    389       DCHECK_GT(e.num_choices, 0);
    390       DCHECK(e.choices);
    391       DCHECK(e.choices[0].command_line_switch);
    392       DCHECK_EQ('\0', e.choices[0].command_line_switch[0]);
    393       break;
    394     default:
    395       NOTREACHED();
    396   }
    397   return true;
    398 }
    399 
    400 // Removes all experiments from prefs::kEnabledLabsExperiments that are
    401 // unknown, to prevent this list to become very long as experiments are added
    402 // and removed.
    403 void SanitizeList(PrefService* prefs) {
    404   std::set<std::string> known_experiments;
    405   for (size_t i = 0; i < num_experiments; ++i) {
    406     DCHECK(ValidateExperiment(experiments[i]));
    407     AddInternalName(experiments[i], &known_experiments);
    408   }
    409 
    410   std::set<std::string> enabled_experiments;
    411   GetEnabledFlags(prefs, &enabled_experiments);
    412 
    413   std::set<std::string> new_enabled_experiments;
    414   std::set_intersection(
    415       known_experiments.begin(), known_experiments.end(),
    416       enabled_experiments.begin(), enabled_experiments.end(),
    417       std::inserter(new_enabled_experiments, new_enabled_experiments.begin()));
    418 
    419   SetEnabledFlags(prefs, new_enabled_experiments);
    420 }
    421 
    422 void GetSanitizedEnabledFlags(
    423     PrefService* prefs, std::set<std::string>* result) {
    424   SanitizeList(prefs);
    425   GetEnabledFlags(prefs, result);
    426 }
    427 
    428 // Variant of GetSanitizedEnabledFlags that also removes any flags that aren't
    429 // enabled on the current platform.
    430 void GetSanitizedEnabledFlagsForCurrentPlatform(
    431     PrefService* prefs, std::set<std::string>* result) {
    432   GetSanitizedEnabledFlags(prefs, result);
    433 
    434   // Filter out any experiments that aren't enabled on the current platform.  We
    435   // don't remove these from prefs else syncing to a platform with a different
    436   // set of experiments would be lossy.
    437   std::set<std::string> platform_experiments;
    438   int current_platform = GetCurrentPlatform();
    439   for (size_t i = 0; i < num_experiments; ++i) {
    440     if (experiments[i].supported_platforms & current_platform)
    441       AddInternalName(experiments[i], &platform_experiments);
    442   }
    443 
    444   std::set<std::string> new_enabled_experiments;
    445   std::set_intersection(
    446       platform_experiments.begin(), platform_experiments.end(),
    447       result->begin(), result->end(),
    448       std::inserter(new_enabled_experiments, new_enabled_experiments.begin()));
    449 
    450   result->swap(new_enabled_experiments);
    451 }
    452 
    453 // Returns the Value representing the choice data in the specified experiment.
    454 Value* CreateChoiceData(const Experiment& experiment,
    455                         const std::set<std::string>& enabled_experiments) {
    456   DCHECK_EQ(Experiment::MULTI_VALUE, experiment.type);
    457   ListValue* result = new ListValue;
    458   for (int i = 0; i < experiment.num_choices; ++i) {
    459     const Experiment::Choice& choice = experiment.choices[i];
    460     DictionaryValue* value = new DictionaryValue;
    461     std::string name = NameForChoice(experiment, i);
    462     value->SetString("description",
    463                      l10n_util::GetStringUTF16(choice.description_id));
    464     value->SetString("internal_name", name);
    465     value->SetBoolean("selected", enabled_experiments.count(name) > 0);
    466     result->Append(value);
    467   }
    468   return result;
    469 }
    470 
    471 }  // namespace
    472 
    473 void ConvertFlagsToSwitches(PrefService* prefs, CommandLine* command_line) {
    474   FlagsState::GetInstance()->ConvertFlagsToSwitches(prefs, command_line);
    475 }
    476 
    477 ListValue* GetFlagsExperimentsData(PrefService* prefs) {
    478   std::set<std::string> enabled_experiments;
    479   GetSanitizedEnabledFlags(prefs, &enabled_experiments);
    480 
    481   int current_platform = GetCurrentPlatform();
    482 
    483   ListValue* experiments_data = new ListValue();
    484   for (size_t i = 0; i < num_experiments; ++i) {
    485     const Experiment& experiment = experiments[i];
    486     if (!(experiment.supported_platforms & current_platform))
    487       continue;
    488 
    489     DictionaryValue* data = new DictionaryValue();
    490     data->SetString("internal_name", experiment.internal_name);
    491     data->SetString("name",
    492                     l10n_util::GetStringUTF16(experiment.visible_name_id));
    493     data->SetString("description",
    494                     l10n_util::GetStringUTF16(
    495                         experiment.visible_description_id));
    496 
    497     switch (experiment.type) {
    498       case Experiment::SINGLE_VALUE:
    499         data->SetBoolean(
    500             "enabled",
    501             enabled_experiments.count(experiment.internal_name) > 0);
    502         break;
    503       case Experiment::MULTI_VALUE:
    504         data->Set("choices", CreateChoiceData(experiment, enabled_experiments));
    505         break;
    506       default:
    507         NOTREACHED();
    508     }
    509 
    510     experiments_data->Append(data);
    511   }
    512   return experiments_data;
    513 }
    514 
    515 bool IsRestartNeededToCommitChanges() {
    516   return FlagsState::GetInstance()->IsRestartNeededToCommitChanges();
    517 }
    518 
    519 void SetExperimentEnabled(
    520     PrefService* prefs, const std::string& internal_name, bool enable) {
    521   FlagsState::GetInstance()->SetExperimentEnabled(prefs, internal_name, enable);
    522 }
    523 
    524 void RemoveFlagsSwitches(
    525     std::map<std::string, CommandLine::StringType>* switch_list) {
    526   FlagsState::GetInstance()->RemoveFlagsSwitches(switch_list);
    527 }
    528 
    529 int GetCurrentPlatform() {
    530 #if defined(OS_MACOSX)
    531   return kOsMac;
    532 #elif defined(OS_WIN)
    533   return kOsWin;
    534 #elif defined(OS_CHROMEOS)  // Needs to be before the OS_LINUX check.
    535   return kOsCrOS;
    536 #elif defined(OS_LINUX)
    537   return kOsLinux;
    538 #else
    539 #error Unknown platform
    540 #endif
    541 }
    542 
    543 void RecordUMAStatistics(const PrefService* prefs) {
    544   std::set<std::string> flags;
    545   GetEnabledFlags(prefs, &flags);
    546   for (std::set<std::string>::iterator it = flags.begin(); it != flags.end();
    547        ++it) {
    548     std::string action("AboutFlags_");
    549     action += *it;
    550     UserMetrics::RecordComputedAction(action);
    551   }
    552   // Since flag metrics are recorded every startup, add a tick so that the
    553   // stats can be made meaningful.
    554   if (flags.size())
    555     UserMetrics::RecordAction(UserMetricsAction("AboutFlags_StartupTick"));
    556   UserMetrics::RecordAction(UserMetricsAction("StartupTick"));
    557 }
    558 
    559 //////////////////////////////////////////////////////////////////////////////
    560 // FlagsState implementation.
    561 
    562 namespace {
    563 
    564 void FlagsState::ConvertFlagsToSwitches(
    565     PrefService* prefs, CommandLine* command_line) {
    566   if (command_line->HasSwitch(switches::kNoExperiments))
    567     return;
    568 
    569   std::set<std::string> enabled_experiments;
    570 
    571   GetSanitizedEnabledFlagsForCurrentPlatform(prefs, &enabled_experiments);
    572 
    573   typedef std::map<std::string, std::pair<std::string, std::string> >
    574       NameToSwitchAndValueMap;
    575   NameToSwitchAndValueMap name_to_switch_map;
    576   for (size_t i = 0; i < num_experiments; ++i) {
    577     const Experiment& e = experiments[i];
    578     if (e.type == Experiment::SINGLE_VALUE) {
    579       name_to_switch_map[e.internal_name] =
    580           std::pair<std::string, std::string>(e.command_line_switch,
    581                                               e.command_line_value);
    582     } else {
    583       for (int j = 0; j < e.num_choices; ++j)
    584         name_to_switch_map[NameForChoice(e, j)] =
    585             std::pair<std::string, std::string>(
    586                 e.choices[j].command_line_switch,
    587                 e.choices[j].command_line_value);
    588     }
    589   }
    590 
    591   command_line->AppendSwitch(switches::kFlagSwitchesBegin);
    592   flags_switches_.insert(
    593       std::pair<std::string, std::string>(switches::kFlagSwitchesBegin,
    594                                           std::string()));
    595   for (std::set<std::string>::iterator it = enabled_experiments.begin();
    596        it != enabled_experiments.end();
    597        ++it) {
    598     const std::string& experiment_name = *it;
    599     NameToSwitchAndValueMap::const_iterator name_to_switch_it =
    600         name_to_switch_map.find(experiment_name);
    601     if (name_to_switch_it == name_to_switch_map.end()) {
    602       NOTREACHED();
    603       continue;
    604     }
    605 
    606     const std::pair<std::string, std::string>&
    607         switch_and_value_pair = name_to_switch_it->second;
    608 
    609     command_line->AppendSwitchASCII(switch_and_value_pair.first,
    610                                     switch_and_value_pair.second);
    611     flags_switches_[switch_and_value_pair.first] = switch_and_value_pair.second;
    612   }
    613   command_line->AppendSwitch(switches::kFlagSwitchesEnd);
    614   flags_switches_.insert(
    615       std::pair<std::string, std::string>(switches::kFlagSwitchesEnd,
    616                                           std::string()));
    617 }
    618 
    619 bool FlagsState::IsRestartNeededToCommitChanges() {
    620   return needs_restart_;
    621 }
    622 
    623 void FlagsState::SetExperimentEnabled(
    624     PrefService* prefs, const std::string& internal_name, bool enable) {
    625   needs_restart_ = true;
    626 
    627   size_t at_index = internal_name.find(about_flags::testing::kMultiSeparator);
    628   if (at_index != std::string::npos) {
    629     DCHECK(enable);
    630     // We're being asked to enable a multi-choice experiment. Disable the
    631     // currently selected choice.
    632     DCHECK_NE(at_index, 0u);
    633     const std::string experiment_name = internal_name.substr(0, at_index);
    634     SetExperimentEnabled(prefs, experiment_name, false);
    635 
    636     // And enable the new choice, if it is not the default first choice.
    637     if (internal_name != experiment_name + "@0") {
    638       std::set<std::string> enabled_experiments;
    639       GetSanitizedEnabledFlags(prefs, &enabled_experiments);
    640       enabled_experiments.insert(internal_name);
    641       SetEnabledFlags(prefs, enabled_experiments);
    642     }
    643     return;
    644   }
    645 
    646   std::set<std::string> enabled_experiments;
    647   GetSanitizedEnabledFlags(prefs, &enabled_experiments);
    648 
    649   const Experiment* e = NULL;
    650   for (size_t i = 0; i < num_experiments; ++i) {
    651     if (experiments[i].internal_name == internal_name) {
    652       e = experiments + i;
    653       break;
    654     }
    655   }
    656   DCHECK(e);
    657 
    658   if (e->type == Experiment::SINGLE_VALUE) {
    659     if (enable)
    660       enabled_experiments.insert(internal_name);
    661     else
    662       enabled_experiments.erase(internal_name);
    663   } else {
    664     if (enable) {
    665       // Enable the first choice.
    666       enabled_experiments.insert(NameForChoice(*e, 0));
    667     } else {
    668       // Find the currently enabled choice and disable it.
    669       for (int i = 0; i < e->num_choices; ++i) {
    670         std::string choice_name = NameForChoice(*e, i);
    671         if (enabled_experiments.find(choice_name) !=
    672             enabled_experiments.end()) {
    673           enabled_experiments.erase(choice_name);
    674           // Continue on just in case there's a bug and more than one
    675           // experiment for this choice was enabled.
    676         }
    677       }
    678     }
    679   }
    680 
    681   SetEnabledFlags(prefs, enabled_experiments);
    682 }
    683 
    684 void FlagsState::RemoveFlagsSwitches(
    685     std::map<std::string, CommandLine::StringType>* switch_list) {
    686   for (std::map<std::string, std::string>::const_iterator
    687            it = flags_switches_.begin(); it != flags_switches_.end(); ++it) {
    688     switch_list->erase(it->first);
    689   }
    690 }
    691 
    692 void FlagsState::reset() {
    693   needs_restart_ = false;
    694   flags_switches_.clear();
    695 }
    696 
    697 } // namespace
    698 
    699 namespace testing {
    700 
    701 // WARNING: '@' is also used in the html file. If you update this constant you
    702 // also need to update the html file.
    703 const char kMultiSeparator[] = "@";
    704 
    705 void ClearState() {
    706   FlagsState::GetInstance()->reset();
    707 }
    708 
    709 void SetExperiments(const Experiment* e, size_t count) {
    710   if (!e) {
    711     experiments = kExperiments;
    712     num_experiments = arraysize(kExperiments);
    713   } else {
    714     experiments = e;
    715     num_experiments = count;
    716   }
    717 }
    718 
    719 const Experiment* GetExperiments(size_t* count) {
    720   *count = num_experiments;
    721   return experiments;
    722 }
    723 
    724 }  // namespace testing
    725 
    726 }  // namespace about_flags
    727