// 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 <utility> #include "base/bind.h" #include "base/bind_helpers.h" #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.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/url_formatter/url_fixer.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/user_metrics.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 "grit/components_strings.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->SetBoolean("showAbout", switches::AboutInSettingsEnabled()); 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(); } 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()) content::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"; } content::RecordComputedAction(metric_string); } void CoreOptionsHandler::NotifyPrefChanged( const std::string& pref_name, const std::string& controlling_pref_name) { scoped_ptr<base::Value> value( CreateValueForPref(pref_name, controlling_pref_name)); DispatchPrefChangeNotification(pref_name, std::move(value)); } void CoreOptionsHandler::DispatchPrefChangeNotification( const std::string& name, scoped_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.Append(new base::StringValue(name)); result_value.Append(value.release()); for (PreferenceCallbackMap::const_iterator iter = range.first; iter != range.second; ++iter) { const std::string& callback_function = iter->second; web_ui()->CallJavascriptFunction(callback_function, result_value); } } 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::Value::CreateNullValue().release(); } const PrefService::Preference* controlling_pref = pref_service->FindPreference(controlling_pref_name); if (!controlling_pref) controlling_pref = pref; base::DictionaryValue* dict = new base::DictionaryValue; dict->Set("value", pref->GetValue()->DeepCopy()); 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).release()); } } else if (controlling_pref->IsRecommended()) { dict->SetString("controlledBy", "recommended"); } const base::Value* recommended_value = controlling_pref->GetRecommendedValue(); if (recommended_value) dict->Set("recommendedValue", recommended_value->DeepCopy()); dict->SetBoolean("disabled", !controlling_pref->IsUserModifiable()); return 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()->CallJavascriptFunction(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; scoped_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::FundamentalValue(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::StringValue(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->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()) content::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::FundamentalValue enabled( plugin_status_pref_setter_.IsClearPluginLSODataEnabled()); web_ui()->CallJavascriptFunction( "options.OptionsPage.setClearPluginLSODataEnabled", enabled); } void CoreOptionsHandler::UpdatePepperFlashSettingsEnabled() { base::FundamentalValue enabled( plugin_status_pref_setter_.IsPepperFlashSettingsEnabled()); web_ui()->CallJavascriptFunction( "options.OptionsPage.setPepperFlashSettingsEnabled", enabled); } bool CoreOptionsHandler::IsUserUnsupervised(const base::Value* to_value) { return !Profile::FromWebUI(web_ui())->IsSupervised(); } } // namespace options