blob: ec45725bbe2fe3946627a53cff091848dc74272f [file] [log] [blame]
// Copyright (c) 2012 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/ui/webui/options/core_options_handler.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/locale_settings.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/url_fixer.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_ui.h"
#include "extensions/browser/extension_pref_value_map.h"
#include "extensions/browser/extension_pref_value_map_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using base::UserMetricsAction;
namespace options {
namespace {
// Whether "controlledBy" property of pref value sent to options web UI needs to
// be set to "extension" when the preference is controlled by an extension.
bool CanSetExtensionControlledPrefValue(
const PrefService::Preference* preference) {
#if defined(OS_WIN)
// These have more obvious UI than the standard one for extension controlled
// values (an extension puzzle piece) on the settings page. To avoiding
// showing the extension puzzle piece for these settings, their "controlledBy"
// value should never be set to "extension".
return preference->name() != prefs::kURLsToRestoreOnStartup &&
preference->name() != prefs::kRestoreOnStartup &&
preference->name() != prefs::kHomePage &&
preference->name() != prefs::kHomePageIsNewTabPage;
#else
return true;
#endif
}
} // namespace
CoreOptionsHandler::CoreOptionsHandler()
: handlers_host_(NULL) {
}
CoreOptionsHandler::~CoreOptionsHandler() {}
void CoreOptionsHandler::InitializeHandler() {
Profile* profile = Profile::FromWebUI(web_ui());
plugin_status_pref_setter_.Init(
profile,
base::Bind(&CoreOptionsHandler::OnPreferenceChanged,
base::Unretained(this),
profile->GetPrefs()));
pref_change_filters_[prefs::kBrowserGuestModeEnabled] =
base::Bind(&CoreOptionsHandler::IsUserUnsupervised,
base::Unretained(this));
pref_change_filters_[prefs::kBrowserAddPersonEnabled] =
base::Bind(&CoreOptionsHandler::IsUserUnsupervised,
base::Unretained(this));
}
void CoreOptionsHandler::InitializePage() {
UpdateClearPluginLSOData();
UpdatePepperFlashSettingsEnabled();
}
void CoreOptionsHandler::GetLocalizedValues(
base::DictionaryValue* localized_strings) {
GetStaticLocalizedValues(localized_strings);
}
void CoreOptionsHandler::GetStaticLocalizedValues(
base::DictionaryValue* localized_strings) {
DCHECK(localized_strings);
// Main
localized_strings->SetString("optionsPageTitle",
l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE));
// Controlled settings bubble.
localized_strings->SetString("controlledSettingPolicy",
l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_POLICY));
localized_strings->SetString("controlledSettingExtension",
l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_EXTENSION));
localized_strings->SetString("controlledSettingExtensionWithName",
l10n_util::GetStringUTF16(
IDS_OPTIONS_CONTROLLED_SETTING_EXTENSION_WITH_NAME));
localized_strings->SetString("controlledSettingManageExtension",
l10n_util::GetStringUTF16(
IDS_OPTIONS_CONTROLLED_SETTING_MANAGE_EXTENSION));
localized_strings->SetString("controlledSettingDisableExtension",
l10n_util::GetStringUTF16(IDS_EXTENSIONS_DISABLE));
localized_strings->SetString("controlledSettingRecommended",
l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_RECOMMENDED));
localized_strings->SetString("controlledSettingHasRecommendation",
l10n_util::GetStringUTF16(
IDS_OPTIONS_CONTROLLED_SETTING_HAS_RECOMMENDATION));
localized_strings->SetString("controlledSettingFollowRecommendation",
l10n_util::GetStringUTF16(
IDS_OPTIONS_CONTROLLED_SETTING_FOLLOW_RECOMMENDATION));
localized_strings->SetString("controlledSettingsPolicy",
l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTINGS_POLICY));
localized_strings->SetString("controlledSettingsExtension",
l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTINGS_EXTENSION));
localized_strings->SetString("controlledSettingsExtensionWithName",
l10n_util::GetStringUTF16(
IDS_OPTIONS_CONTROLLED_SETTINGS_EXTENSION_WITH_NAME));
// Search
RegisterTitle(localized_strings, "searchPage", IDS_OPTIONS_SEARCH_PAGE_TITLE);
localized_strings->SetString("searchPlaceholder",
l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PLACEHOLDER));
localized_strings->SetString("searchPageNoMatches",
l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PAGE_NO_MATCHES));
localized_strings->SetString("searchPageHelpLabel",
l10n_util::GetStringUTF16(IDS_OPTIONS_SEARCH_PAGE_HELP_LABEL));
localized_strings->SetString("searchPageHelpTitle",
l10n_util::GetStringFUTF16(IDS_OPTIONS_SEARCH_PAGE_HELP_TITLE,
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
localized_strings->SetString("searchPageHelpURL",
chrome::kSettingsSearchHelpURL);
// About
localized_strings->SetString("aboutButton",
l10n_util::GetStringUTF16(IDS_ABOUT_BUTTON));
// Common
localized_strings->SetString("ok",
l10n_util::GetStringUTF16(IDS_OK));
localized_strings->SetString("cancel",
l10n_util::GetStringUTF16(IDS_CANCEL));
localized_strings->SetString("learnMore",
l10n_util::GetStringUTF16(IDS_LEARN_MORE));
localized_strings->SetString("close",
l10n_util::GetStringUTF16(IDS_CLOSE));
localized_strings->SetString("done",
l10n_util::GetStringUTF16(IDS_DONE));
localized_strings->SetString("deletableItemDeleteButtonTitle",
l10n_util::GetStringUTF16(IDS_OPTIONS_DELETABLE_ITEM_DELETE_BUTTON));
}
void CoreOptionsHandler::Uninitialize() {
std::string last_pref;
for (PreferenceCallbackMap::const_iterator iter = pref_callback_map_.begin();
iter != pref_callback_map_.end();
++iter) {
if (last_pref != iter->first) {
StopObservingPref(iter->first);
last_pref = iter->first;
}
}
}
void CoreOptionsHandler::OnPreferenceChanged(PrefService* service,
const std::string& pref_name) {
if (pref_name == prefs::kClearPluginLSODataEnabled) {
// This preference is stored in Local State, not in the user preferences.
UpdateClearPluginLSOData();
return;
}
if (pref_name == prefs::kPepperFlashSettingsEnabled) {
UpdatePepperFlashSettingsEnabled();
return;
}
NotifyPrefChanged(pref_name, std::string());
}
void CoreOptionsHandler::RegisterMessages() {
registrar_.Init(Profile::FromWebUI(web_ui())->GetPrefs());
local_state_registrar_.Init(g_browser_process->local_state());
web_ui()->RegisterMessageCallback("coreOptionsInitialize",
base::Bind(&CoreOptionsHandler::HandleInitialize,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("onFinishedLoadingOptions",
base::Bind(&CoreOptionsHandler::OnFinishedLoading,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("fetchPrefs",
base::Bind(&CoreOptionsHandler::HandleFetchPrefs,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("observePrefs",
base::Bind(&CoreOptionsHandler::HandleObservePrefs,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("setBooleanPref",
base::Bind(&CoreOptionsHandler::HandleSetBooleanPref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("setIntegerPref",
base::Bind(&CoreOptionsHandler::HandleSetIntegerPref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("setDoublePref",
base::Bind(&CoreOptionsHandler::HandleSetDoublePref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("setStringPref",
base::Bind(&CoreOptionsHandler::HandleSetStringPref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("setURLPref",
base::Bind(&CoreOptionsHandler::HandleSetURLPref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("setListPref",
base::Bind(&CoreOptionsHandler::HandleSetListPref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("clearPref",
base::Bind(&CoreOptionsHandler::HandleClearPref,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("coreOptionsUserMetricsAction",
base::Bind(&CoreOptionsHandler::HandleUserMetricsAction,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("disableExtension",
base::Bind(&CoreOptionsHandler::HandleDisableExtension,
base::Unretained(this)));
}
void CoreOptionsHandler::HandleInitialize(const base::ListValue* args) {
DCHECK(handlers_host_);
handlers_host_->InitializeHandlers();
}
void CoreOptionsHandler::OnFinishedLoading(const base::ListValue* args) {
DCHECK(handlers_host_);
handlers_host_->OnFinishedLoading();
}
std::unique_ptr<base::Value> CoreOptionsHandler::FetchPref(
const std::string& pref_name) {
return CreateValueForPref(pref_name, std::string());
}
void CoreOptionsHandler::ObservePref(const std::string& pref_name) {
if (g_browser_process->local_state()->FindPreference(pref_name)) {
local_state_registrar_.Add(
pref_name,
base::Bind(&CoreOptionsHandler::OnPreferenceChanged,
base::Unretained(this),
local_state_registrar_.prefs()));
}
// TODO(pneubeck): change this to if/else once kProxy is only used as a user
// pref. Currently, it is both a user and a local state pref.
if (Profile::FromWebUI(web_ui())->GetPrefs()->FindPreference(pref_name)) {
registrar_.Add(
pref_name,
base::Bind(&CoreOptionsHandler::OnPreferenceChanged,
base::Unretained(this),
registrar_.prefs()));
}
}
void CoreOptionsHandler::StopObservingPref(const std::string& pref_name) {
if (g_browser_process->local_state()->FindPreference(pref_name))
local_state_registrar_.Remove(pref_name);
else
registrar_.Remove(pref_name);
}
void CoreOptionsHandler::SetPref(const std::string& pref_name,
const base::Value* value,
const std::string& metric) {
PrefService* pref_service = FindServiceForPref(pref_name);
PrefChangeFilterMap::iterator iter = pref_change_filters_.find(pref_name);
if (iter != pref_change_filters_.end()) {
// Also check if the pref is user modifiable (don't even try to run the
// filter function if the user is not allowed to change the pref).
const PrefService::Preference* pref =
pref_service->FindPreference(pref_name);
if ((pref && !pref->IsUserModifiable()) || !iter->second.Run(value)) {
// Reject the change; remind the page of the true value.
NotifyPrefChanged(pref_name, std::string());
return;
}
}
switch (value->GetType()) {
case base::Value::Type::BOOLEAN:
case base::Value::Type::INTEGER:
case base::Value::Type::DOUBLE:
case base::Value::Type::STRING:
case base::Value::Type::LIST:
pref_service->Set(pref_name, *value);
break;
default:
NOTREACHED();
return;
}
ProcessUserMetric(value, metric);
}
void CoreOptionsHandler::ClearPref(const std::string& pref_name,
const std::string& metric) {
PrefService* pref_service = FindServiceForPref(pref_name);
pref_service->ClearPref(pref_name);
if (!metric.empty())
base::RecordComputedAction(metric);
}
void CoreOptionsHandler::ProcessUserMetric(const base::Value* value,
const std::string& metric) {
if (metric.empty())
return;
std::string metric_string = metric;
if (value->IsType(base::Value::Type::BOOLEAN)) {
bool bool_value;
CHECK(value->GetAsBoolean(&bool_value));
metric_string += bool_value ? "_Enable" : "_Disable";
}
base::RecordComputedAction(metric_string);
}
void CoreOptionsHandler::NotifyPrefChanged(
const std::string& pref_name,
const std::string& controlling_pref_name) {
std::unique_ptr<base::Value> value(
CreateValueForPref(pref_name, controlling_pref_name));
DispatchPrefChangeNotification(pref_name, std::move(value));
}
void CoreOptionsHandler::DispatchPrefChangeNotification(
const std::string& name,
std::unique_ptr<base::Value> value) {
std::pair<PreferenceCallbackMap::const_iterator,
PreferenceCallbackMap::const_iterator> range =
pref_callback_map_.equal_range(name);
base::ListValue result_value;
result_value.AppendString(name);
result_value.Append(std::move(value));
for (PreferenceCallbackMap::const_iterator iter = range.first;
iter != range.second; ++iter) {
const std::string& callback_function = iter->second;
web_ui()->CallJavascriptFunctionUnsafe(callback_function, result_value);
}
}
std::unique_ptr<base::Value> CoreOptionsHandler::CreateValueForPref(
const std::string& pref_name,
const std::string& controlling_pref_name) {
const PrefService* pref_service = FindServiceForPref(pref_name);
const PrefService::Preference* pref =
pref_service->FindPreference(pref_name);
if (!pref) {
NOTREACHED();
return base::MakeUnique<base::Value>();
}
const PrefService::Preference* controlling_pref =
pref_service->FindPreference(controlling_pref_name);
if (!controlling_pref)
controlling_pref = pref;
auto dict = base::MakeUnique<base::DictionaryValue>();
dict->Set("value", base::MakeUnique<base::Value>(*pref->GetValue()));
if (controlling_pref->IsManaged()) {
dict->SetString("controlledBy", "policy");
} else if (controlling_pref->IsExtensionControlled() &&
CanSetExtensionControlledPrefValue(controlling_pref)) {
Profile* profile = Profile::FromWebUI(web_ui());
ExtensionPrefValueMap* extension_pref_value_map =
ExtensionPrefValueMapFactory::GetForBrowserContext(profile);
std::string extension_id =
extension_pref_value_map->GetExtensionControllingPref(
controlling_pref->name());
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile)->GetExtensionById(
extension_id, extensions::ExtensionRegistry::EVERYTHING);
if (extension) {
dict->SetString("controlledBy", "extension");
dict->Set("extension", extensions::util::GetExtensionInfo(extension));
}
} else if (controlling_pref->IsRecommended()) {
dict->SetString("controlledBy", "recommended");
}
const base::Value* recommended_value =
controlling_pref->GetRecommendedValue();
if (recommended_value)
dict->Set("recommendedValue",
base::MakeUnique<base::Value>(*recommended_value));
dict->SetBoolean("disabled", !controlling_pref->IsUserModifiable());
return std::move(dict);
}
PrefService* CoreOptionsHandler::FindServiceForPref(
const std::string& pref_name) {
// Proxy is a peculiar case: on ChromeOS, settings exist in both user
// prefs and local state, but chrome://settings should affect only user prefs.
// Elsewhere the proxy settings are stored in local state.
// See http://crbug.com/157147
PrefService* user_prefs = Profile::FromWebUI(web_ui())->GetPrefs();
if (pref_name == proxy_config::prefs::kProxy)
#if defined(OS_CHROMEOS)
return user_prefs;
#else
return g_browser_process->local_state();
#endif
// Find which PrefService contains the given pref. Pref names should not
// be duplicated across services, however if they are, prefer the user's
// prefs.
if (user_prefs->FindPreference(pref_name))
return user_prefs;
if (g_browser_process->local_state()->FindPreference(pref_name))
return g_browser_process->local_state();
return user_prefs;
}
void CoreOptionsHandler::HandleFetchPrefs(const base::ListValue* args) {
// First param is name of callback function, so, there needs to be at least
// one more element for the actual preference identifier.
DCHECK_GE(static_cast<int>(args->GetSize()), 2);
// Get callback JS function name.
const base::Value* callback;
if (!args->Get(0, &callback) || !callback->IsType(base::Value::Type::STRING))
return;
base::string16 callback_function;
if (!callback->GetAsString(&callback_function))
return;
// Get the list of name for prefs to build the response dictionary.
base::DictionaryValue result_value;
const base::Value* list_member;
for (size_t i = 1; i < args->GetSize(); i++) {
if (!args->Get(i, &list_member))
break;
if (!list_member->IsType(base::Value::Type::STRING))
continue;
std::string pref_name;
if (!list_member->GetAsString(&pref_name))
continue;
result_value.Set(pref_name, FetchPref(pref_name));
}
web_ui()->CallJavascriptFunctionUnsafe(base::UTF16ToASCII(callback_function),
result_value);
}
void CoreOptionsHandler::HandleObservePrefs(const base::ListValue* args) {
// First param is name is JS callback function name, the rest are pref
// identifiers that we are observing.
DCHECK_GE(static_cast<int>(args->GetSize()), 2);
// Get preference change callback function name.
std::string callback_func_name;
if (!args->GetString(0, &callback_func_name))
return;
// Get all other parameters - pref identifiers.
for (size_t i = 1; i < args->GetSize(); i++) {
const base::Value* list_member;
if (!args->Get(i, &list_member))
break;
// Just ignore bad pref identifiers for now.
std::string pref_name;
if (!list_member->IsType(base::Value::Type::STRING) ||
!list_member->GetAsString(&pref_name))
continue;
if (pref_callback_map_.find(pref_name) == pref_callback_map_.end())
ObservePref(pref_name);
pref_callback_map_.insert(
PreferenceCallbackMap::value_type(pref_name, callback_func_name));
}
}
void CoreOptionsHandler::HandleSetBooleanPref(const base::ListValue* args) {
HandleSetPref(args, TYPE_BOOLEAN);
}
void CoreOptionsHandler::HandleSetIntegerPref(const base::ListValue* args) {
HandleSetPref(args, TYPE_INTEGER);
}
void CoreOptionsHandler::HandleSetDoublePref(const base::ListValue* args) {
HandleSetPref(args, TYPE_DOUBLE);
}
void CoreOptionsHandler::HandleSetStringPref(const base::ListValue* args) {
HandleSetPref(args, TYPE_STRING);
}
void CoreOptionsHandler::HandleSetURLPref(const base::ListValue* args) {
HandleSetPref(args, TYPE_URL);
}
void CoreOptionsHandler::HandleSetListPref(const base::ListValue* args) {
HandleSetPref(args, TYPE_LIST);
}
void CoreOptionsHandler::HandleSetPref(const base::ListValue* args,
PrefType type) {
DCHECK_GT(static_cast<int>(args->GetSize()), 1);
std::string pref_name;
if (!args->GetString(0, &pref_name))
return;
const base::Value* value;
if (!args->Get(1, &value))
return;
std::unique_ptr<base::Value> temp_value;
switch (type) {
case TYPE_BOOLEAN:
if (!value->IsType(base::Value::Type::BOOLEAN)) {
NOTREACHED();
return;
}
break;
case TYPE_INTEGER: {
// In JS all numbers are doubles.
double double_value;
if (!value->GetAsDouble(&double_value)) {
NOTREACHED();
return;
}
int int_value = static_cast<int>(double_value);
temp_value.reset(new base::Value(int_value));
value = temp_value.get();
break;
}
case TYPE_DOUBLE:
if (!value->IsType(base::Value::Type::DOUBLE)) {
NOTREACHED();
return;
}
break;
case TYPE_STRING:
if (!value->IsType(base::Value::Type::STRING)) {
NOTREACHED();
return;
}
break;
case TYPE_URL: {
std::string original;
if (!value->GetAsString(&original)) {
NOTREACHED();
return;
}
GURL fixed = url_formatter::FixupURL(original, std::string());
temp_value.reset(new base::Value(fixed.spec()));
value = temp_value.get();
break;
}
case TYPE_LIST: {
// In case we have a List pref we got a JSON string.
std::string json_string;
if (!value->GetAsString(&json_string)) {
NOTREACHED();
return;
}
temp_value = base::JSONReader::Read(json_string);
value = temp_value.get();
if (!value || !value->IsType(base::Value::Type::LIST)) {
NOTREACHED();
return;
}
break;
}
default:
NOTREACHED();
}
std::string metric;
if (args->GetSize() > 2 && !args->GetString(2, &metric))
LOG(WARNING) << "Invalid metric parameter: " << pref_name;
SetPref(pref_name, value, metric);
}
void CoreOptionsHandler::HandleClearPref(const base::ListValue* args) {
DCHECK_GT(static_cast<int>(args->GetSize()), 0);
std::string pref_name;
if (!args->GetString(0, &pref_name))
return;
std::string metric;
if (args->GetSize() > 1) {
if (!args->GetString(1, &metric))
NOTREACHED();
}
ClearPref(pref_name, metric);
}
void CoreOptionsHandler::HandleUserMetricsAction(const base::ListValue* args) {
std::string metric = base::UTF16ToUTF8(ExtractStringValue(args));
if (!metric.empty())
base::RecordComputedAction(metric);
}
void CoreOptionsHandler::HandleDisableExtension(const base::ListValue* args) {
std::string extension_id;
if (args->GetString(0, &extension_id)) {
ExtensionService* extension_service = extensions::ExtensionSystem::Get(
Profile::FromWebUI(web_ui()))->extension_service();
DCHECK(extension_service);
extension_service->DisableExtension(
extension_id, extensions::Extension::DISABLE_USER_ACTION);
} else {
NOTREACHED();
}
}
void CoreOptionsHandler::UpdateClearPluginLSOData() {
base::Value enabled(plugin_status_pref_setter_.IsClearPluginLSODataEnabled());
web_ui()->CallJavascriptFunctionUnsafe(
"options.OptionsPage.setClearPluginLSODataEnabled", enabled);
}
void CoreOptionsHandler::UpdatePepperFlashSettingsEnabled() {
base::Value enabled(
plugin_status_pref_setter_.IsPepperFlashSettingsEnabled());
web_ui()->CallJavascriptFunctionUnsafe(
"options.OptionsPage.setPepperFlashSettingsEnabled", enabled);
}
bool CoreOptionsHandler::IsUserUnsupervised(const base::Value* to_value) {
return !Profile::FromWebUI(web_ui())->IsSupervised();
}
} // namespace options