blob: ddf05203ef9342c2dd3bf9e90269fa9c75c8adc3 [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/labs.h"
#include <algorithm>
#include <iterator>
#include <map>
#include <set>
#include "app/l10n_util.h"
#include "base/command_line.h"
#include "base/values.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "grit/generated_resources.h"
namespace about_labs {
enum { kOsMac = 1 << 0, kOsWin = 1 << 1, kOsLinux = 1 << 2 };
unsigned kOsAll = kOsMac | kOsWin | kOsLinux;
struct Experiment {
// The internal name of the experiment. This is never shown to the user.
// It _is_ however stored in the prefs file, so you shouldn't change the
// name of existing labs.
const char* internal_name;
// String id of the message containing the experiment's name.
int visible_name_id;
// String id of the message containing the experiment's description.
int visible_description_id;
// The platforms the experiment is available on
// Needs to be more than a compile-time #ifdef because of profile sync.
unsigned supported_platforms; // bitmask
// The commandline parameter that's added when this lab is active. This is
// different from |internal_name| so that the commandline flag can be
// renamed without breaking the prefs file.
const char* command_line;
};
const Experiment kExperiments[] = {
{
"expose-for-tabs", // Do not change; see above.
IDS_LABS_TABPOSE_NAME,
IDS_LABS_TABPOSE_DESCRIPTION,
kOsMac,
#if defined(OS_MACOSX)
// The switch exists only on OS X.
switches::kEnableExposeForTabs
#else
""
#endif
},
{
"vertical-tabs", // Do not change; see above.
IDS_LABS_SIDE_TABS_NAME,
IDS_LABS_SIDE_TABS_DESCRIPTION,
kOsWin,
switches::kEnableVerticalTabs
},
{
"tabbed-options", // Do not change; see above.
IDS_LABS_TABBED_OPTIONS_NAME,
IDS_LABS_TABBED_OPTIONS_DESCRIPTION,
kOsAll,
switches::kEnableTabbedOptions
},
{
"match-preview", // Do not change; see above.
IDS_LABS_INSTANT_NAME,
IDS_LABS_INSTANT_DESCRIPTION,
kOsWin,
switches::kEnableMatchPreview
},
{
"remoting", // Do not change; see above.
IDS_LABS_REMOTING_NAME,
#if defined(OS_WINDOWS)
// Windows only supports host functionality at the moment.
IDS_LABS_REMOTING_HOST_DESCRIPTION,
#elif defined(OS_LINUX)
// Linux only supports client functionality at the moment.
IDS_LABS_REMOTING_CLIENT_DESCRIPTION,
#else
// On other platforms, this lab isn't available at all.
0,
#endif
kOsWin | kOsLinux,
switches::kEnableRemoting
},
};
// Extracts the list of enabled lab experiments from a profile and stores them
// in a set.
void GetEnabledLabs(const PrefService* prefs, std::set<std::string>* result) {
const ListValue* enabled_experiments = prefs->GetList(
prefs::kEnabledLabsExperiments);
if (!enabled_experiments)
return;
for (ListValue::const_iterator it = enabled_experiments->begin();
it != enabled_experiments->end();
++it) {
std::string experiment_name;
if (!(*it)->GetAsString(&experiment_name)) {
LOG(WARNING) << "Invalid entry in " << prefs::kEnabledLabsExperiments;
continue;
}
result->insert(experiment_name);
}
}
// Takes a set of enabled lab experiments
void SetEnabledLabs(
PrefService* prefs, const std::set<std::string>& enabled_experiments) {
ListValue* experiments_list = prefs->GetMutableList(
prefs::kEnabledLabsExperiments);
if (!experiments_list)
return;
experiments_list->Clear();
for (std::set<std::string>::const_iterator it = enabled_experiments.begin();
it != enabled_experiments.end();
++it) {
experiments_list->Append(new StringValue(*it));
}
}
// Removes all experiments from prefs::kEnabledLabsExperiments that are
// unknown, to prevent this list to become very long as experiments are added
// and removed.
void SanitizeList(PrefService* prefs) {
std::set<std::string> known_experiments;
for (size_t i = 0; i < arraysize(kExperiments); ++i)
known_experiments.insert(kExperiments[i].internal_name);
std::set<std::string> enabled_experiments;
GetEnabledLabs(prefs, &enabled_experiments);
std::set<std::string> new_enabled_experiments;
std::set_intersection(
known_experiments.begin(), known_experiments.end(),
enabled_experiments.begin(), enabled_experiments.end(),
std::inserter(new_enabled_experiments, new_enabled_experiments.begin()));
SetEnabledLabs(prefs, new_enabled_experiments);
}
void GetSanitizedEnabledLabs(
PrefService* prefs, std::set<std::string>* result) {
SanitizeList(prefs);
GetEnabledLabs(prefs, result);
}
int GetCurrentPlatform() {
#if defined(OS_MACOSX)
return kOsMac;
#elif defined(OS_WIN)
return kOsWin;
#elif defined(OS_LINUX)
return kOsLinux;
#else
#error Unknown platform
#endif
}
bool IsEnabled() {
#if defined(OS_CHROMEOS)
// ChromeOS uses a different mechanism for about:labs; integrated with their
// dom ui options.
return false;
#elif defined(GOOGLE_CHROME_BUILD)
// Don't enable this on the stable channel.
return !platform_util::GetVersionStringModifier().empty();
#else
return true;
#endif
}
void ConvertLabsToSwitches(Profile* profile, CommandLine* command_line) {
// Do not activate labs features on the stable channel.
if (!IsEnabled())
return;
std::set<std::string> enabled_experiments;
GetSanitizedEnabledLabs(profile->GetPrefs(), &enabled_experiments);
std::map<std::string, const Experiment*> experiments;
for (size_t i = 0; i < arraysize(kExperiments); ++i)
experiments[kExperiments[i].internal_name] = &kExperiments[i];
for (std::set<std::string>::iterator it = enabled_experiments.begin();
it != enabled_experiments.end();
++it) {
const std::string& experiment_name = *it;
std::map<std::string, const Experiment*>::iterator experiment =
experiments.find(experiment_name);
DCHECK(experiment != experiments.end());
if (experiment == experiments.end())
continue;
command_line->AppendSwitch(experiment->second->command_line);
}
}
ListValue* GetLabsExperimentsData(Profile* profile) {
std::set<std::string> enabled_experiments;
GetSanitizedEnabledLabs(profile->GetPrefs(), &enabled_experiments);
int current_platform = GetCurrentPlatform();
ListValue* experiments_data = new ListValue();
for (size_t i = 0; i < arraysize(kExperiments); ++i) {
const Experiment& experiment = kExperiments[i];
if (!(experiment.supported_platforms & current_platform))
continue;
DictionaryValue* data = new DictionaryValue();
data->SetString("internal_name", experiment.internal_name);
data->SetString("name",
l10n_util::GetStringUTF16(experiment.visible_name_id));
data->SetString("description",
l10n_util::GetStringUTF16(
experiment.visible_description_id));
data->SetBoolean("enabled",
enabled_experiments.count(experiment.internal_name) > 0);
experiments_data->Append(data);
}
return experiments_data;
}
static bool needs_restart_ = false;
bool IsRestartNeededToCommitChanges() {
return needs_restart_;
}
void SetExperimentEnabled(
Profile* profile, const std::string& internal_name, bool enable) {
needs_restart_ = true;
std::set<std::string> enabled_experiments;
GetSanitizedEnabledLabs(profile->GetPrefs(), &enabled_experiments);
if (enable)
enabled_experiments.insert(internal_name);
else
enabled_experiments.erase(internal_name);
SetEnabledLabs(profile->GetPrefs(), enabled_experiments);
}
} // namespace Labs