diff options
Diffstat (limited to 'chrome/browser/prefs')
27 files changed, 4551 insertions, 0 deletions
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc new file mode 100644 index 0000000..93bb6be --- /dev/null +++ b/chrome/browser/prefs/browser_prefs.cc @@ -0,0 +1,140 @@ +// 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/prefs/browser_prefs.h" + +#include "chrome/browser/autofill/autofill_manager.h" +#include "chrome/browser/background_contents_service.h" +#include "chrome/browser/background_mode_manager.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_shutdown.h" +#include "chrome/browser/cookie_modal_dialog.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/dom_ui/new_tab_ui.h" +#include "chrome/browser/dom_ui/plugins_ui.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/browser/extensions/extension_dom_ui.h" +#include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/extensions/extensions_ui.h" +#include "chrome/browser/external_protocol_handler.h" +#include "chrome/browser/geolocation/geolocation_content_settings_map.h" +#include "chrome/browser/geolocation/geolocation_prefs.h" +#include "chrome/browser/google_url_tracker.h" +#include "chrome/browser/gtk/certificate_manager.h" +#include "chrome/browser/host_content_settings_map.h" +#include "chrome/browser/host_zoom_map.h" +#include "chrome/browser/intranet_redirect_detector.h" +#include "chrome/browser/metrics/metrics_log.h" +#include "chrome/browser/metrics/metrics_service.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/net/predictor_api.h" +#include "chrome/browser/notifications/desktop_notification_service.h" +#include "chrome/browser/page_info_model.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/prefs/session_startup_pref.h" +#include "chrome/browser/renderer_host/browser_render_process_host.h" +#include "chrome/browser/renderer_host/web_cache_manager.h" +#include "chrome/browser/safe_browsing/safe_browsing_service.h" +#include "chrome/browser/search_engines/keyword_editor_controller.h" +#include "chrome/browser/search_engines/template_url_prepopulate_data.h" +#include "chrome/browser/ssl/ssl_manager.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tabs/pinned_tab_codec.h" +#include "chrome/browser/task_manager.h" +#include "chrome/browser/translate/translate_prefs.h" +#include "chrome/browser/upgrade_detector.h" + +#if defined(TOOLKIT_VIEWS) // TODO(port): whittle this down as we port +#include "chrome/browser/views/browser_actions_container.h" +#include "chrome/browser/views/frame/browser_view.h" +#endif + +#if defined(TOOLKIT_GTK) +#include "chrome/browser/gtk/browser_window_gtk.h" +#endif + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/login/apply_services_customization.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/login/wizard_controller.h" +#include "chrome/browser/chromeos/preferences.h" +#include "chrome/browser/chromeos/status/language_menu_button.h" +#endif + +namespace browser { + +void RegisterAllPrefs(PrefService* user_prefs, PrefService* local_state) { + RegisterLocalState(local_state); + RegisterUserPrefs(user_prefs); +} + +void RegisterLocalState(PrefService* local_state) { + // Prefs in Local State + Browser::RegisterPrefs(local_state); + WebCacheManager::RegisterPrefs(local_state); + ExternalProtocolHandler::RegisterPrefs(local_state); + GoogleURLTracker::RegisterPrefs(local_state); + IntranetRedirectDetector::RegisterPrefs(local_state); + KeywordEditorController::RegisterPrefs(local_state); + MetricsLog::RegisterPrefs(local_state); + MetricsService::RegisterPrefs(local_state); + SafeBrowsingService::RegisterPrefs(local_state); + browser_shutdown::RegisterPrefs(local_state); + chrome_browser_net::RegisterPrefs(local_state); + PageInfoModel::RegisterPrefs(local_state); +#if defined(TOOLKIT_VIEWS) + BrowserView::RegisterBrowserViewPrefs(local_state); +#endif + UpgradeDetector::RegisterPrefs(local_state); + TaskManager::RegisterPrefs(local_state); + geolocation::RegisterPrefs(local_state); + AutoFillManager::RegisterBrowserPrefs(local_state); +#if defined(OS_CHROMEOS) + chromeos::UserManager::RegisterPrefs(local_state); + WizardController::RegisterPrefs(local_state); + chromeos::LanguageMenuButton::RegisterPrefs(local_state); + chromeos::ApplyServicesCustomization::RegisterPrefs(local_state); +#endif +} + +void RegisterUserPrefs(PrefService* user_prefs) { + // User prefs + AutoFillManager::RegisterUserPrefs(user_prefs); + BackgroundModeManager::RegisterUserPrefs(user_prefs); + SessionStartupPref::RegisterUserPrefs(user_prefs); + Browser::RegisterUserPrefs(user_prefs); + PasswordManager::RegisterUserPrefs(user_prefs); + chrome_browser_net::RegisterUserPrefs(user_prefs); + DownloadManager::RegisterUserPrefs(user_prefs); + bookmark_utils::RegisterUserPrefs(user_prefs); + TabContents::RegisterUserPrefs(user_prefs); + TemplateURLPrepopulateData::RegisterUserPrefs(user_prefs); + ExtensionDOMUI::RegisterUserPrefs(user_prefs); + ExtensionsUI::RegisterUserPrefs(user_prefs); + NewTabUI::RegisterUserPrefs(user_prefs); + PluginsUI::RegisterUserPrefs(user_prefs); + HostContentSettingsMap::RegisterUserPrefs(user_prefs); + HostZoomMap::RegisterUserPrefs(user_prefs); + DevToolsManager::RegisterUserPrefs(user_prefs); + PinnedTabCodec::RegisterUserPrefs(user_prefs); + ExtensionPrefs::RegisterUserPrefs(user_prefs); + GeolocationContentSettingsMap::RegisterUserPrefs(user_prefs); + TranslatePrefs::RegisterUserPrefs(user_prefs); + DesktopNotificationService::RegisterUserPrefs(user_prefs); + ChromeURLRequestContextGetter::RegisterUserPrefs(user_prefs); +#if defined(TOOLKIT_VIEWS) + BrowserActionsContainer::RegisterUserPrefs(user_prefs); +#elif defined(TOOLKIT_GTK) + BrowserWindowGtk::RegisterUserPrefs(user_prefs); + certificate_manager_util::RegisterUserPrefs(user_prefs); +#endif +#if defined(OS_CHROMEOS) + chromeos::Preferences::RegisterUserPrefs(user_prefs); +#endif + BackgroundContentsService::RegisterUserPrefs(user_prefs); + CookiePromptModalDialog::RegisterUserPrefs(user_prefs); +} + +} // namespace browser diff --git a/chrome/browser/prefs/browser_prefs.h b/chrome/browser/prefs/browser_prefs.h new file mode 100644 index 0000000..ef109a4 --- /dev/null +++ b/chrome/browser/prefs/browser_prefs.h @@ -0,0 +1,20 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_PREFS_BROWSER_PREFS_H__ +#define CHROME_BROWSER_PREFS_BROWSER_PREFS_H__ +#pragma once + +class PrefService; + +namespace browser { + +// Makes the PrefService objects aware of all the prefs. +void RegisterAllPrefs(PrefService* user_prefs, PrefService* local_state); +void RegisterLocalState(PrefService* local_state); +void RegisterUserPrefs(PrefService* user_prefs); + +} // namespace browser + +#endif // CHROME_BROWSER_PREFS_BROWSER_PREFS_H__ diff --git a/chrome/browser/prefs/command_line_pref_store.cc b/chrome/browser/prefs/command_line_pref_store.cc new file mode 100644 index 0000000..41770e9 --- /dev/null +++ b/chrome/browser/prefs/command_line_pref_store.cc @@ -0,0 +1,66 @@ +// 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/prefs/command_line_pref_store.h" + +#include "app/app_switches.h" +#include "base/values.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" + +const CommandLinePrefStore::StringSwitchToPreferenceMapEntry + CommandLinePrefStore::string_switch_map_[] = { + { switches::kLang, prefs::kApplicationLocale }, + { switches::kProxyServer, prefs::kProxyServer }, + { switches::kProxyPacUrl, prefs::kProxyPacUrl }, + { switches::kProxyBypassList, prefs::kProxyBypassList }, +}; + +const CommandLinePrefStore::BooleanSwitchToPreferenceMapEntry + CommandLinePrefStore::boolean_switch_map_[] = { + { switches::kNoProxyServer, prefs::kNoProxyServer, true }, + { switches::kProxyAutoDetect, prefs::kProxyAutoDetect, true }, +}; + +CommandLinePrefStore::CommandLinePrefStore(const CommandLine* command_line) + : command_line_(command_line), + prefs_(new DictionaryValue()) {} + +PrefStore::PrefReadError CommandLinePrefStore::ReadPrefs() { + ApplySimpleSwitches(); + ValidateProxySwitches(); + return PrefStore::PREF_READ_ERROR_NONE; +} + +void CommandLinePrefStore::ApplySimpleSwitches() { + // Look for each switch we know about and set its preference accordingly. + for (size_t i = 0; i < arraysize(string_switch_map_); ++i) { + if (command_line_->HasSwitch(string_switch_map_[i].switch_name)) { + Value* value = Value::CreateStringValue(command_line_-> + GetSwitchValueASCII(string_switch_map_[i].switch_name)); + prefs_->Set(string_switch_map_[i].preference_path, value); + } + } + + for (size_t i = 0; i < arraysize(boolean_switch_map_); ++i) { + if (command_line_->HasSwitch(boolean_switch_map_[i].switch_name)) { + Value* value = Value::CreateBooleanValue( + boolean_switch_map_[i].set_value); + prefs_->Set(boolean_switch_map_[i].preference_path, value); + } + } +} + +bool CommandLinePrefStore::ValidateProxySwitches() { + if (command_line_->HasSwitch(switches::kNoProxyServer) && + (command_line_->HasSwitch(switches::kProxyAutoDetect) || + command_line_->HasSwitch(switches::kProxyServer) || + command_line_->HasSwitch(switches::kProxyPacUrl) || + command_line_->HasSwitch(switches::kProxyBypassList))) { + LOG(WARNING) << "Additional command-line proxy switches specified when --" + << switches::kNoProxyServer << " was also specified."; + return false; + } + return true; +} diff --git a/chrome/browser/prefs/command_line_pref_store.h b/chrome/browser/prefs/command_line_pref_store.h new file mode 100644 index 0000000..89e49ce --- /dev/null +++ b/chrome/browser/prefs/command_line_pref_store.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef CHROME_BROWSER_PREFS_COMMAND_LINE_PREF_STORE_H_ +#define CHROME_BROWSER_PREFS_COMMAND_LINE_PREF_STORE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/scoped_ptr.h" +#include "chrome/common/pref_store.h" + +class DictionaryValue; + +// This PrefStore keeps track of preferences set by command-line switches, +// such as proxy settings. +class CommandLinePrefStore : public PrefStore { + public: + explicit CommandLinePrefStore(const CommandLine* command_line); + virtual ~CommandLinePrefStore() {} + + // PrefStore methods: + virtual PrefReadError ReadPrefs(); + virtual DictionaryValue* prefs() { return prefs_.get(); } + + protected: + // Logs a message and returns false if the proxy switches are + // self-contradictory. Protected so it can be used in unit testing. + bool ValidateProxySwitches(); + + private: + // Weak reference. + const CommandLine* command_line_; + + scoped_ptr<DictionaryValue> prefs_; + + struct StringSwitchToPreferenceMapEntry { + const char* switch_name; + const char* preference_path; + }; + static const StringSwitchToPreferenceMapEntry string_switch_map_[]; + + // |set_value| indicates what the preference should be set to if the switch + // is present. + struct BooleanSwitchToPreferenceMapEntry { + const char* switch_name; + const char* preference_path; + bool set_value; + }; + static const BooleanSwitchToPreferenceMapEntry boolean_switch_map_[]; + + // Using the string and boolean maps, apply command-line switches to their + // corresponding preferences in this pref store. + void ApplySimpleSwitches(); + + DISALLOW_COPY_AND_ASSIGN(CommandLinePrefStore); +}; + +#endif // CHROME_BROWSER_PREFS_COMMAND_LINE_PREF_STORE_H_ diff --git a/chrome/browser/prefs/command_line_pref_store_unittest.cc b/chrome/browser/prefs/command_line_pref_store_unittest.cc new file mode 100644 index 0000000..c6d2bd2 --- /dev/null +++ b/chrome/browser/prefs/command_line_pref_store_unittest.cc @@ -0,0 +1,130 @@ +// 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 <gtest/gtest.h> + +#include "app/app_switches.h" +#include "base/command_line.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/prefs/command_line_pref_store.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" + +namespace { + +class TestCommandLinePrefStore : public CommandLinePrefStore { + public: + explicit TestCommandLinePrefStore(CommandLine* cl) + : CommandLinePrefStore(cl) {} + + bool ProxySwitchesAreValid() { + return ValidateProxySwitches(); + } +}; + +const char unknown_bool[] = "unknown_switch"; +const char unknown_string[] = "unknown_other_switch"; + +} // namespace + +// Tests a simple string pref on the command line. +TEST(CommandLinePrefStoreTest, SimpleStringPref) { + CommandLine cl(CommandLine::ARGUMENTS_ONLY); + cl.AppendSwitchASCII(switches::kLang, "hi-MOM"); + CommandLinePrefStore store(&cl); + EXPECT_EQ(store.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + + std::string result; + EXPECT_TRUE(store.prefs()->GetString(prefs::kApplicationLocale, &result)); + EXPECT_EQ("hi-MOM", result); +} + +// Tests a simple boolean pref on the command line. +TEST(CommandLinePrefStoreTest, SimpleBooleanPref) { + CommandLine cl(CommandLine::ARGUMENTS_ONLY); + cl.AppendSwitch(switches::kNoProxyServer); + CommandLinePrefStore store(&cl); + EXPECT_EQ(store.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + + bool result; + EXPECT_TRUE(store.prefs()->GetBoolean(prefs::kNoProxyServer, &result)); + EXPECT_TRUE(result); +} + +// Tests a command line with no recognized prefs. +TEST(CommandLinePrefStoreTest, NoPrefs) { + CommandLine cl(CommandLine::ARGUMENTS_ONLY); + cl.AppendSwitch(unknown_string); + cl.AppendSwitchASCII(unknown_bool, "a value"); + CommandLinePrefStore store(&cl); + EXPECT_EQ(store.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + + bool bool_result = false; + EXPECT_FALSE(store.prefs()->GetBoolean(unknown_bool, &bool_result)); + EXPECT_FALSE(bool_result); + + std::string string_result = ""; + EXPECT_FALSE(store.prefs()->GetString(unknown_string, &string_result)); + EXPECT_EQ("", string_result); +} + +// Tests a complex command line with multiple known and unknown switches. +TEST(CommandLinePrefStoreTest, MultipleSwitches) { + CommandLine cl(CommandLine::ARGUMENTS_ONLY); + cl.AppendSwitch(unknown_string); + cl.AppendSwitch(switches::kProxyAutoDetect); + cl.AppendSwitchASCII(switches::kProxyServer, "proxy"); + cl.AppendSwitchASCII(switches::kProxyBypassList, "list"); + cl.AppendSwitchASCII(unknown_bool, "a value"); + CommandLinePrefStore store(&cl); + EXPECT_EQ(store.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + + bool bool_result = false; + EXPECT_FALSE(store.prefs()->GetBoolean(unknown_bool, &bool_result)); + EXPECT_FALSE(bool_result); + EXPECT_TRUE(store.prefs()->GetBoolean(prefs::kProxyAutoDetect, &bool_result)); + EXPECT_TRUE(bool_result); + + std::string string_result = ""; + EXPECT_FALSE(store.prefs()->GetString(unknown_string, &string_result)); + EXPECT_EQ("", string_result); + EXPECT_TRUE(store.prefs()->GetString(prefs::kProxyServer, &string_result)); + EXPECT_EQ("proxy", string_result); + EXPECT_TRUE(store.prefs()->GetString(prefs::kProxyBypassList, + &string_result)); + EXPECT_EQ("list", string_result); +} + +// Tests proxy switch validation. +TEST(CommandLinePrefStoreTest, ProxySwitchValidation) { + CommandLine cl(CommandLine::ARGUMENTS_ONLY); + + // No switches. + TestCommandLinePrefStore store(&cl); + EXPECT_EQ(store.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + EXPECT_TRUE(store.ProxySwitchesAreValid()); + + // Only no-proxy. + cl.AppendSwitch(switches::kNoProxyServer); + TestCommandLinePrefStore store2(&cl); + EXPECT_EQ(store2.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + EXPECT_TRUE(store2.ProxySwitchesAreValid()); + + // Another proxy switch too. + cl.AppendSwitch(switches::kProxyAutoDetect); + TestCommandLinePrefStore store3(&cl); + EXPECT_EQ(store3.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + EXPECT_FALSE(store3.ProxySwitchesAreValid()); + + // All proxy switches except no-proxy. + CommandLine cl2(CommandLine::ARGUMENTS_ONLY); + cl2.AppendSwitch(switches::kProxyAutoDetect); + cl2.AppendSwitchASCII(switches::kProxyServer, "server"); + cl2.AppendSwitchASCII(switches::kProxyPacUrl, "url"); + cl2.AppendSwitchASCII(switches::kProxyBypassList, "list"); + TestCommandLinePrefStore store4(&cl2); + EXPECT_EQ(store4.ReadPrefs(), PrefStore::PREF_READ_ERROR_NONE); + EXPECT_TRUE(store4.ProxySwitchesAreValid()); +} diff --git a/chrome/browser/prefs/dummy_pref_store.cc b/chrome/browser/prefs/dummy_pref_store.cc new file mode 100644 index 0000000..3a0a81a --- /dev/null +++ b/chrome/browser/prefs/dummy_pref_store.cc @@ -0,0 +1,22 @@ +// 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/prefs/dummy_pref_store.h" + +#include "base/values.h" + +DummyPrefStore::DummyPrefStore() + : prefs_(new DictionaryValue()), + read_only_(true), + prefs_written_(false) { } + +PrefStore::PrefReadError DummyPrefStore::ReadPrefs() { + prefs_.reset(new DictionaryValue()); + return PrefStore::PREF_READ_ERROR_NONE; +} + +bool DummyPrefStore::WritePrefs() { + prefs_written_ = true; + return prefs_written_; +} diff --git a/chrome/browser/prefs/dummy_pref_store.h b/chrome/browser/prefs/dummy_pref_store.h new file mode 100644 index 0000000..ebf92fc --- /dev/null +++ b/chrome/browser/prefs/dummy_pref_store.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef CHROME_BROWSER_PREFS_DUMMY_PREF_STORE_H_ +#define CHROME_BROWSER_PREFS_DUMMY_PREF_STORE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/common/pref_store.h" + +class DictionaryValue; + +// |DummyPrefStore| is a stub implementation of the |PrefStore| interface. +// It allows to get and set the state of the |PrefStore|. +class DummyPrefStore : public PrefStore { + public: + DummyPrefStore(); + + virtual ~DummyPrefStore() {} + + virtual DictionaryValue* prefs() { return prefs_.get(); } + + virtual PrefStore::PrefReadError ReadPrefs(); + + virtual bool ReadOnly() { return read_only_; } + + virtual bool WritePrefs(); + + // Getter and Setter methods for setting and getting the state of the + // |DummyPrefStore|. + virtual void set_read_only(bool read_only) { read_only_ = read_only; } + virtual void set_prefs(DictionaryValue* prefs) { prefs_.reset(prefs); } + virtual void set_prefs_written(bool status) { prefs_written_ = status; } + virtual bool get_prefs_written() { return prefs_written_; } + + private: + scoped_ptr<DictionaryValue> prefs_; + + // Flag that indicates if the PrefStore is read-only + bool read_only_; + + // Flag that indicates if the method WritePrefs was called. + bool prefs_written_; + + DISALLOW_COPY_AND_ASSIGN(DummyPrefStore); +}; + +#endif // CHROME_BROWSER_PREFS_DUMMY_PREF_STORE_H_ diff --git a/chrome/browser/prefs/pref_member.cc b/chrome/browser/prefs/pref_member.cc new file mode 100644 index 0000000..f326419 --- /dev/null +++ b/chrome/browser/prefs/pref_member.cc @@ -0,0 +1,132 @@ +// 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/prefs/pref_member.h" + +#include "base/logging.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/notification_type.h" + +namespace subtle { + +PrefMemberBase::PrefMemberBase() + : observer_(NULL), + prefs_(NULL), + is_synced_(false), + setting_value_(false) { +} + +PrefMemberBase::~PrefMemberBase() { + if (!pref_name_.empty()) + prefs_->RemovePrefObserver(pref_name_.c_str(), this); +} + + +void PrefMemberBase::Init(const char* pref_name, PrefService* prefs, + NotificationObserver* observer) { + DCHECK(pref_name); + DCHECK(prefs); + DCHECK(pref_name_.empty()); // Check that Init is only called once. + observer_ = observer; + prefs_ = prefs; + pref_name_ = pref_name; + DCHECK(!pref_name_.empty()); + + // Add ourself as a pref observer so we can keep our local value in sync. + prefs_->AddPrefObserver(pref_name, this); +} + +bool PrefMemberBase::IsManaged() { + DCHECK(!pref_name_.empty()); + const PrefService::Preference* pref = + prefs_->FindPreference(pref_name_.c_str()); + return pref && pref->IsManaged(); +} + +void PrefMemberBase::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(!pref_name_.empty()); + DCHECK(NotificationType::PREF_CHANGED == type); + UpdateValueFromPref(); + is_synced_ = true; + if (!setting_value_ && observer_) + observer_->Observe(type, source, details); +} + +void PrefMemberBase::VerifyValuePrefName() { + DCHECK(!pref_name_.empty()); +} + +} // namespace subtle + +BooleanPrefMember::BooleanPrefMember() : PrefMember<bool>() { +} + +BooleanPrefMember::~BooleanPrefMember() { +} + +void BooleanPrefMember::UpdateValueFromPref() { + value_ = prefs()->GetBoolean(pref_name().c_str()); +} + +void BooleanPrefMember::UpdatePref(const bool& value) { + prefs()->SetBoolean(pref_name().c_str(), value); +} + +IntegerPrefMember::IntegerPrefMember() : PrefMember<int>() { +} + +IntegerPrefMember::~IntegerPrefMember() { +} + +void IntegerPrefMember::UpdateValueFromPref() { + value_ = prefs()->GetInteger(pref_name().c_str()); +} + +void IntegerPrefMember::UpdatePref(const int& value) { + prefs()->SetInteger(pref_name().c_str(), value); +} + +RealPrefMember::RealPrefMember() : PrefMember<double>() { +} + +RealPrefMember::~RealPrefMember() { +} + +void RealPrefMember::UpdateValueFromPref() { + value_ = prefs()->GetReal(pref_name().c_str()); +} + +void RealPrefMember::UpdatePref(const double& value) { + prefs()->SetReal(pref_name().c_str(), value); +} + +StringPrefMember::StringPrefMember() : PrefMember<std::string>() { +} + +StringPrefMember::~StringPrefMember() { +} + +void StringPrefMember::UpdateValueFromPref() { + value_ = prefs()->GetString(pref_name().c_str()); +} + +void StringPrefMember::UpdatePref(const std::string& value) { + prefs()->SetString(pref_name().c_str(), value); +} + +FilePathPrefMember::FilePathPrefMember() : PrefMember<FilePath>() { +} + +FilePathPrefMember::~FilePathPrefMember() { +} + +void FilePathPrefMember::UpdateValueFromPref() { + value_ = prefs()->GetFilePath(pref_name().c_str()); +} + +void FilePathPrefMember::UpdatePref(const FilePath& value) { + prefs()->SetFilePath(pref_name().c_str(), value); +} diff --git a/chrome/browser/prefs/pref_member.h b/chrome/browser/prefs/pref_member.h new file mode 100644 index 0000000..1a4e385 --- /dev/null +++ b/chrome/browser/prefs/pref_member.h @@ -0,0 +1,208 @@ +// 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. +// +// A helper class that stays in sync with a preference (bool, int, real, +// string or filepath). For example: +// +// class MyClass { +// public: +// MyClass(PrefService* prefs) { +// my_string_.Init(prefs::kHomePage, prefs, NULL /* no observer */); +// } +// private: +// StringPrefMember my_string_; +// }; +// +// my_string_ should stay in sync with the prefs::kHomePage pref and will +// update if either the pref changes or if my_string_.SetValue is called. +// +// An optional observer can be passed into the Init method which can be used to +// notify MyClass of changes. Note that if you use SetValue(), the observer +// will not be notified. + +#ifndef CHROME_BROWSER_PREFS_PREF_MEMBER_H_ +#define CHROME_BROWSER_PREFS_PREF_MEMBER_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "chrome/common/notification_observer.h" + +class PrefService; + +namespace subtle { + +class PrefMemberBase : public NotificationObserver { + protected: + PrefMemberBase(); + virtual ~PrefMemberBase(); + + // See PrefMember<> for description. + void Init(const char* pref_name, PrefService* prefs, + NotificationObserver* observer); + + // See PrefMember<> for description. + bool IsManaged(); + + // NotificationObserver + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + void VerifyValuePrefName(); + + // This methods is used to do the actual sync with pref of the specified type. + virtual void UpdateValueFromPref() = 0; + + const std::string& pref_name() const { return pref_name_; } + PrefService* prefs() { return prefs_; } + + // Ordered the members to compact the class instance. + private: + std::string pref_name_; + NotificationObserver* observer_; + PrefService* prefs_; + + protected: + bool is_synced_; + bool setting_value_; +}; + +} // namespace subtle + + +template <typename ValueType> +class PrefMember : public subtle::PrefMemberBase { + public: + // Defer initialization to an Init method so it's easy to make this class be + // a member variable. + PrefMember() { } + virtual ~PrefMember() { } + + // Do the actual initialization of the class. |observer| may be null if you + // don't want any notifications of changes. + void Init(const char* pref_name, PrefService* prefs, + NotificationObserver* observer) { + subtle::PrefMemberBase::Init(pref_name, prefs, observer); + } + + // Check whether the pref is managed, i.e. controlled externally through + // enterprise configuration management (e.g. windows group policy). Returns + // false for unknown prefs. + bool IsManaged() { + return subtle::PrefMemberBase::IsManaged(); + } + + // Retrieve the value of the member variable. + ValueType GetValue() { + VerifyValuePrefName(); + // We lazily fetch the value from the pref service the first time GetValue + // is called. + if (!is_synced_) { + UpdateValueFromPref(); + is_synced_ = true; + } + return value_; + } + + // Provided as a convenience. + ValueType operator*() { + return GetValue(); + } + + // Set the value of the member variable. + void SetValue(const ValueType& value) { + VerifyValuePrefName(); + setting_value_ = true; + UpdatePref(value); + setting_value_ = false; + } + + // Set the value of the member variable if it is not managed. + void SetValueIfNotManaged(const ValueType& value) { + if (!IsManaged()) { + SetValue(value); + } + } + + protected: + // This methods is used to do the actual sync with pref of the specified type. + virtual void UpdatePref(const ValueType& value) = 0; + + // We cache the value of the pref so we don't have to keep walking the pref + // tree. + ValueType value_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Implementations of Boolean, Integer, Real, and String PrefMember below. + +class BooleanPrefMember : public PrefMember<bool> { + public: + BooleanPrefMember(); + virtual ~BooleanPrefMember(); + + protected: + virtual void UpdateValueFromPref(); + virtual void UpdatePref(const bool& value); + + private: + DISALLOW_COPY_AND_ASSIGN(BooleanPrefMember); +}; + +class IntegerPrefMember : public PrefMember<int> { + public: + IntegerPrefMember(); + virtual ~IntegerPrefMember(); + + protected: + virtual void UpdateValueFromPref(); + virtual void UpdatePref(const int& value); + + private: + DISALLOW_COPY_AND_ASSIGN(IntegerPrefMember); +}; + +class RealPrefMember : public PrefMember<double> { + public: + RealPrefMember(); + virtual ~RealPrefMember(); + + protected: + virtual void UpdateValueFromPref(); + virtual void UpdatePref(const double& value); + + private: + DISALLOW_COPY_AND_ASSIGN(RealPrefMember); +}; + +class StringPrefMember : public PrefMember<std::string> { + public: + StringPrefMember(); + virtual ~StringPrefMember(); + + protected: + virtual void UpdateValueFromPref(); + virtual void UpdatePref(const std::string& value); + + private: + DISALLOW_COPY_AND_ASSIGN(StringPrefMember); +}; + +class FilePathPrefMember : public PrefMember<FilePath> { + public: + FilePathPrefMember(); + virtual ~FilePathPrefMember(); + + protected: + virtual void UpdateValueFromPref(); + virtual void UpdatePref(const FilePath& value); + + private: + DISALLOW_COPY_AND_ASSIGN(FilePathPrefMember); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_MEMBER_H_ diff --git a/chrome/browser/prefs/pref_member_unittest.cc b/chrome/browser/prefs/pref_member_unittest.cc new file mode 100644 index 0000000..f59f937 --- /dev/null +++ b/chrome/browser/prefs/pref_member_unittest.cc @@ -0,0 +1,194 @@ +// 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/prefs/dummy_pref_store.h" +#include "chrome/browser/prefs/pref_member.h" +#include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/common/notification_service.h" +#include "chrome/test/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static const char kBoolPref[] = "bool"; +static const char kIntPref[] = "int"; +static const char kRealPref[] = "real"; +static const char kStringPref[] = "string"; + +void RegisterTestPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(kBoolPref, false); + prefs->RegisterIntegerPref(kIntPref, 0); + prefs->RegisterRealPref(kRealPref, 0.0); + prefs->RegisterStringPref(kStringPref, "default"); +} + +class PrefMemberTestClass : public NotificationObserver { + public: + explicit PrefMemberTestClass(PrefService* prefs) + : observe_cnt_(0), prefs_(prefs) { + str_.Init(kStringPref, prefs, this); + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(NotificationType::PREF_CHANGED == type); + PrefService* prefs_in = Source<PrefService>(source).ptr(); + EXPECT_EQ(prefs_in, prefs_); + std::string* pref_name_in = Details<std::string>(details).ptr(); + EXPECT_EQ(*pref_name_in, kStringPref); + EXPECT_EQ(str_.GetValue(), prefs_->GetString(kStringPref)); + ++observe_cnt_; + } + + StringPrefMember str_; + int observe_cnt_; + + private: + PrefService* prefs_; +}; + +} // anonymous namespace + +TEST(PrefMemberTest, BasicGetAndSet) { + TestingPrefService prefs; + RegisterTestPrefs(&prefs); + + // Test bool + BooleanPrefMember boolean; + boolean.Init(kBoolPref, &prefs, NULL); + + // Check the defaults + EXPECT_FALSE(prefs.GetBoolean(kBoolPref)); + EXPECT_FALSE(boolean.GetValue()); + EXPECT_FALSE(*boolean); + + // Try changing through the member variable. + boolean.SetValue(true); + EXPECT_TRUE(boolean.GetValue()); + EXPECT_TRUE(prefs.GetBoolean(kBoolPref)); + EXPECT_TRUE(*boolean); + + // Try changing back through the pref. + prefs.SetBoolean(kBoolPref, false); + EXPECT_FALSE(prefs.GetBoolean(kBoolPref)); + EXPECT_FALSE(boolean.GetValue()); + EXPECT_FALSE(*boolean); + + // Test int + IntegerPrefMember integer; + integer.Init(kIntPref, &prefs, NULL); + + // Check the defaults + EXPECT_EQ(0, prefs.GetInteger(kIntPref)); + EXPECT_EQ(0, integer.GetValue()); + EXPECT_EQ(0, *integer); + + // Try changing through the member variable. + integer.SetValue(5); + EXPECT_EQ(5, integer.GetValue()); + EXPECT_EQ(5, prefs.GetInteger(kIntPref)); + EXPECT_EQ(5, *integer); + + // Try changing back through the pref. + prefs.SetInteger(kIntPref, 2); + EXPECT_EQ(2, prefs.GetInteger(kIntPref)); + EXPECT_EQ(2, integer.GetValue()); + EXPECT_EQ(2, *integer); + + // Test real (double) + RealPrefMember real; + real.Init(kRealPref, &prefs, NULL); + + // Check the defaults + EXPECT_EQ(0.0, prefs.GetReal(kRealPref)); + EXPECT_EQ(0.0, real.GetValue()); + EXPECT_EQ(0.0, *real); + + // Try changing through the member variable. + real.SetValue(1.0); + EXPECT_EQ(1.0, real.GetValue()); + EXPECT_EQ(1.0, prefs.GetReal(kRealPref)); + EXPECT_EQ(1.0, *real); + + // Try changing back through the pref. + prefs.SetReal(kRealPref, 3.0); + EXPECT_EQ(3.0, prefs.GetReal(kRealPref)); + EXPECT_EQ(3.0, real.GetValue()); + EXPECT_EQ(3.0, *real); + + // Test string + StringPrefMember string; + string.Init(kStringPref, &prefs, NULL); + + // Check the defaults + EXPECT_EQ("default", prefs.GetString(kStringPref)); + EXPECT_EQ("default", string.GetValue()); + EXPECT_EQ("default", *string); + + // Try changing through the member variable. + string.SetValue("foo"); + EXPECT_EQ("foo", string.GetValue()); + EXPECT_EQ("foo", prefs.GetString(kStringPref)); + EXPECT_EQ("foo", *string); + + // Try changing back through the pref. + prefs.SetString(kStringPref, "bar"); + EXPECT_EQ("bar", prefs.GetString(kStringPref)); + EXPECT_EQ("bar", string.GetValue()); + EXPECT_EQ("bar", *string); +} + +TEST(PrefMemberTest, TwoPrefs) { + // Make sure two RealPrefMembers stay in sync. + TestingPrefService prefs; + RegisterTestPrefs(&prefs); + + RealPrefMember pref1; + pref1.Init(kRealPref, &prefs, NULL); + RealPrefMember pref2; + pref2.Init(kRealPref, &prefs, NULL); + + pref1.SetValue(2.3); + EXPECT_EQ(2.3, *pref2); + + pref2.SetValue(3.5); + EXPECT_EQ(3.5, *pref1); + + prefs.SetReal(kRealPref, 4.2); + EXPECT_EQ(4.2, *pref1); + EXPECT_EQ(4.2, *pref2); +} + +TEST(PrefMemberTest, Observer) { + TestingPrefService prefs; + RegisterTestPrefs(&prefs); + + PrefMemberTestClass test_obj(&prefs); + EXPECT_EQ("default", *test_obj.str_); + + // Calling SetValue should not fire the observer. + test_obj.str_.SetValue("hello"); + EXPECT_EQ(0, test_obj.observe_cnt_); + EXPECT_EQ("hello", prefs.GetString(kStringPref)); + + // Changing the pref does fire the observer. + prefs.SetString(kStringPref, "world"); + EXPECT_EQ(1, test_obj.observe_cnt_); + EXPECT_EQ("world", *(test_obj.str_)); + + // Not changing the value should not fire the observer. + prefs.SetString(kStringPref, "world"); + EXPECT_EQ(1, test_obj.observe_cnt_); + EXPECT_EQ("world", *(test_obj.str_)); + + prefs.SetString(kStringPref, "hello"); + EXPECT_EQ(2, test_obj.observe_cnt_); + EXPECT_EQ("hello", prefs.GetString(kStringPref)); +} + +TEST(PrefMemberTest, NoInit) { + // Make sure not calling Init on a PrefMember doesn't cause problems. + IntegerPrefMember pref; +} diff --git a/chrome/browser/prefs/pref_notifier.cc b/chrome/browser/prefs/pref_notifier.cc new file mode 100644 index 0000000..665bffb --- /dev/null +++ b/chrome/browser/prefs/pref_notifier.cc @@ -0,0 +1,140 @@ +// 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/prefs/pref_notifier.h" + +#include "base/stl_util-inl.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/policy/configuration_policy_pref_store.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/common/notification_service.h" + + +PrefNotifier::PrefNotifier(PrefService* service, PrefValueStore* value_store) + : pref_service_(service), + pref_value_store_(value_store) { + registrar_.Add(this, + NotificationType(NotificationType::POLICY_CHANGED), + NotificationService::AllSources()); +} + +PrefNotifier::~PrefNotifier() { + DCHECK(CalledOnValidThread()); + + // Verify that there are no pref observers when we shut down. + for (PrefObserverMap::iterator it = pref_observers_.begin(); + it != pref_observers_.end(); ++it) { + NotificationObserverList::Iterator obs_iterator(*(it->second)); + if (obs_iterator.GetNext()) { + LOG(WARNING) << "pref observer found at shutdown " << it->first; + } + } + + STLDeleteContainerPairSecondPointers(pref_observers_.begin(), + pref_observers_.end()); + pref_observers_.clear(); +} + +void PrefNotifier::OnPreferenceSet(const char* pref_name, + PrefNotifier::PrefStoreType new_store, + const Value* old_value) { + if (pref_value_store_->PrefHasChanged(pref_name, new_store, old_value)) + FireObservers(pref_name); +} + +void PrefNotifier::OnUserPreferenceSet(const char* pref_name, + const Value* old_value) { + OnPreferenceSet(pref_name, PrefNotifier::USER_STORE, old_value); +} + +void PrefNotifier::FireObservers(const char* path) { + DCHECK(CalledOnValidThread()); + + // Convert path to a std::string because the Details constructor requires a + // class. + std::string path_str(path); + PrefObserverMap::iterator observer_iterator = pref_observers_.find(path_str); + if (observer_iterator == pref_observers_.end()) + return; + + NotificationObserverList::Iterator it(*(observer_iterator->second)); + NotificationObserver* observer; + while ((observer = it.GetNext()) != NULL) { + observer->Observe(NotificationType::PREF_CHANGED, + Source<PrefService>(pref_service_), + Details<std::string>(&path_str)); + } +} + +void PrefNotifier::AddPrefObserver(const char* path, + NotificationObserver* obs) { + // Get the pref observer list associated with the path. + NotificationObserverList* observer_list = NULL; + PrefObserverMap::iterator observer_iterator = pref_observers_.find(path); + if (observer_iterator == pref_observers_.end()) { + observer_list = new NotificationObserverList; + pref_observers_[path] = observer_list; + } else { + observer_list = observer_iterator->second; + } + + // Verify that this observer doesn't already exist. + NotificationObserverList::Iterator it(*observer_list); + NotificationObserver* existing_obs; + while ((existing_obs = it.GetNext()) != NULL) { + DCHECK(existing_obs != obs) << path << " observer already registered"; + if (existing_obs == obs) + return; + } + + // Ok, safe to add the pref observer. + observer_list->AddObserver(obs); +} + +void PrefNotifier::RemovePrefObserver(const char* path, + NotificationObserver* obs) { + DCHECK(CalledOnValidThread()); + + PrefObserverMap::iterator observer_iterator = pref_observers_.find(path); + if (observer_iterator == pref_observers_.end()) { + return; + } + + NotificationObserverList* observer_list = observer_iterator->second; + observer_list->RemoveObserver(obs); +} + +void PrefNotifier::FireObserversForRefreshedManagedPrefs( + std::vector<std::string> changed_prefs_paths) { + DCHECK(CalledOnValidThread()); + std::vector<std::string>::const_iterator current; + for (current = changed_prefs_paths.begin(); + current != changed_prefs_paths.end(); + ++current) { + FireObservers(current->c_str()); + } +} + +void PrefNotifier::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::POLICY_CHANGED) { + PrefValueStore::AfterRefreshCallback* callback = + NewCallback(this, + &PrefNotifier::FireObserversForRefreshedManagedPrefs); + // The notification of the policy refresh can come from any thread, + // but the manipulation of the PrefValueStore must happen on the UI + // thread, thus the policy refresh must be explicitly started on it. + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + pref_value_store_, + &PrefValueStore::RefreshPolicyPrefs, + ConfigurationPolicyPrefStore::CreateManagedPolicyPrefStore(), + ConfigurationPolicyPrefStore::CreateRecommendedPolicyPrefStore(), + + callback)); + } +} diff --git a/chrome/browser/prefs/pref_notifier.h b/chrome/browser/prefs/pref_notifier.h new file mode 100644 index 0000000..c627de9 --- /dev/null +++ b/chrome/browser/prefs/pref_notifier.h @@ -0,0 +1,112 @@ +// 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. + +#ifndef CHROME_BROWSER_PREFS_PREF_NOTIFIER_H_ +#define CHROME_BROWSER_PREFS_PREF_NOTIFIER_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/hash_tables.h" +#include "base/non_thread_safe.h" +#include "base/observer_list.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class NotificationObserver; +class PrefService; +class PrefValueStore; +class Value; + +// Registers observers for particular preferences and sends notifications when +// preference values or sources (i.e., which preference layer controls the +// preference) change. +class PrefNotifier : public NonThreadSafe, + public NotificationObserver { + public: + // PrefStores must be listed here in order from highest to lowest priority. + // MANAGED contains all managed (policy) preference values. + // EXTENSION contains preference values set by extensions. + // COMMAND_LINE contains preference values set by command-line switches. + // USER contains all user-set preference values. + // RECOMMENDED contains all recommended (policy) preference values. + // This enum is kept in pref_notifier.h rather than pref_value_store.h in + // order to minimize additional headers needed by the *PrefStore files. + enum PrefStoreType { + // INVALID_STORE is not associated with an actual PrefStore but used as + // an invalid marker, e.g. as a return value. + INVALID_STORE = -1, + MANAGED_STORE = 0, + EXTENSION_STORE, + COMMAND_LINE_STORE, + USER_STORE, + RECOMMENDED_STORE, + PREF_STORE_TYPE_MAX = RECOMMENDED_STORE + }; + + // The |service| with which this notifier is associated will be sent as the + // source of any notifications. + PrefNotifier(PrefService* service, PrefValueStore* value_store); + + virtual ~PrefNotifier(); + + // For the given pref_name, fire any observer of the pref if |old_value| is + // different from the current value, or if the store controlling the value + // has changed. + // TODO(pamg): Currently notifies in some cases when it shouldn't. See + // comments for PrefHasChanged() in pref_value_store.h. + void OnPreferenceSet(const char* pref_name, + PrefNotifier::PrefStoreType new_store, + const Value* old_value); + + // Convenience method to be called when a preference is set in the + // USER_STORE. See OnPreferenceSet(). + void OnUserPreferenceSet(const char* pref_name, const Value* old_value); + + // For the given pref_name, fire any observer of the pref. Virtual so it can + // be mocked for unit testing. + virtual void FireObservers(const char* path); + + // If the pref at the given path changes, we call the observer's Observe + // method with NOTIFY_PREF_CHANGED. + void AddPrefObserver(const char* path, NotificationObserver* obs); + void RemovePrefObserver(const char* path, NotificationObserver* obs); + + protected: + // A map from pref names to a list of observers. Observers get fired in the + // order they are added. These should only be accessed externally for unit + // testing. + typedef ObserverList<NotificationObserver> NotificationObserverList; + typedef base::hash_map<std::string, NotificationObserverList*> + PrefObserverMap; + const PrefObserverMap* pref_observers() { return &pref_observers_; } + + private: + // Weak references. + PrefService* pref_service_; + PrefValueStore* pref_value_store_; + + NotificationRegistrar registrar_; + + PrefObserverMap pref_observers_; + + // Called after a policy refresh to notify relevant preference observers. + // |changed_prefs_paths| is the vector of preference paths changed by the + // policy update. It is passed by value and not reference because + // this method is called asynchronously as a callback from another thread. + // Copying the vector guarantees that the vector's lifecycle spans the + // method's invocation. + void FireObserversForRefreshedManagedPrefs( + std::vector<std::string> changed_prefs_paths); + + // NotificationObserver methods: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + DISALLOW_COPY_AND_ASSIGN(PrefNotifier); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_NOTIFIER_H_ diff --git a/chrome/browser/prefs/pref_notifier_unittest.cc b/chrome/browser/prefs/pref_notifier_unittest.cc new file mode 100644 index 0000000..5a1a6f4 --- /dev/null +++ b/chrome/browser/prefs/pref_notifier_unittest.cc @@ -0,0 +1,290 @@ +// 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/prefs/pref_notifier.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/notification_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace { + +const char kChangedPref[] = "changed_pref"; +const char kUnchangedPref[] = "unchanged_pref"; + +bool DetailsAreChangedPref(const Details<std::string>& details) { + std::string* string_in = Details<std::string>(details).ptr(); + return strcmp(string_in->c_str(), kChangedPref) == 0; +} + +// Test PrefNotifier that allows tracking of observers and notifications. +class MockPrefNotifier : public PrefNotifier { + public: + MockPrefNotifier(PrefService* prefs, PrefValueStore* value_store) + : PrefNotifier(prefs, value_store) {} + + virtual ~MockPrefNotifier() {} + + MOCK_METHOD1(FireObservers, void(const char* path)); + + void RealFireObservers(const char* path) { + PrefNotifier::FireObservers(path); + } + + size_t CountObserver(const char* path, NotificationObserver* obs) { + PrefObserverMap::const_iterator observer_iterator = + pref_observers()->find(path); + if (observer_iterator == pref_observers()->end()) + return false; + + NotificationObserverList* observer_list = observer_iterator->second; + NotificationObserverList::Iterator it(*observer_list); + NotificationObserver* existing_obs; + size_t count = 0; + while ((existing_obs = it.GetNext()) != NULL) { + if (existing_obs == obs) + count++; + } + + return count; + } +}; + +// Mock PrefValueStore that has no PrefStores and injects a simpler +// PrefHasChanged(). +class MockPrefValueStore : public PrefValueStore { + public: + MockPrefValueStore() : PrefValueStore(NULL, NULL, NULL, NULL, NULL) {} + + virtual ~MockPrefValueStore() {} + + // This mock version returns true if the pref path starts with "changed". + virtual bool PrefHasChanged(const char* path, + PrefNotifier::PrefStoreType new_store, + const Value* old_value) { + std::string path_str(path); + if (path_str.compare(0, 7, "changed") == 0) + return true; + return false; + } +}; + +// Mock PrefService that allows the PrefNotifier to be injected. +class MockPrefService : public PrefService { + public: + explicit MockPrefService(PrefValueStore* pref_value_store) + : PrefService(pref_value_store) {} + + void SetPrefNotifier(PrefNotifier* notifier) { + pref_notifier_.reset(notifier); + } +}; + +// Mock PrefObserver that verifies notifications. +class MockPrefObserver : public NotificationObserver { + public: + virtual ~MockPrefObserver() {} + + MOCK_METHOD3(Observe, void(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details)); +}; + +// Test fixture class. +class PrefNotifierTest : public testing::Test { + protected: + virtual void SetUp() { + value_store_ = new MockPrefValueStore; + pref_service_.reset(new MockPrefService(value_store_)); + notifier_ = new MockPrefNotifier(pref_service_.get(), value_store_); + pref_service_->SetPrefNotifier(notifier_); + } + + // The PrefService takes ownership of the PrefValueStore and PrefNotifier. + PrefValueStore* value_store_; + MockPrefNotifier* notifier_; + scoped_ptr<MockPrefService> pref_service_; + + MockPrefObserver obs1_; + MockPrefObserver obs2_; +}; + + +TEST_F(PrefNotifierTest, OnPreferenceSet) { + EXPECT_CALL(*notifier_, FireObservers(kChangedPref)) + .Times(PrefNotifier::PREF_STORE_TYPE_MAX + 1); + EXPECT_CALL(*notifier_, FireObservers(kUnchangedPref)).Times(0); + + for (size_t i = 0; i <= PrefNotifier::PREF_STORE_TYPE_MAX; ++i) { + notifier_->OnPreferenceSet(kChangedPref, + static_cast<PrefNotifier::PrefStoreType>(i), NULL); + notifier_->OnPreferenceSet(kUnchangedPref, + static_cast<PrefNotifier::PrefStoreType>(i), NULL); + } +} + +TEST_F(PrefNotifierTest, OnUserPreferenceSet) { + EXPECT_CALL(*notifier_, FireObservers(kChangedPref)); + EXPECT_CALL(*notifier_, FireObservers(kUnchangedPref)).Times(0); + notifier_->OnUserPreferenceSet(kChangedPref, NULL); + notifier_->OnUserPreferenceSet(kUnchangedPref, NULL); +} + +TEST_F(PrefNotifierTest, AddAndRemovePrefObservers) { + const char pref_name[] = "homepage"; + const char pref_name2[] = "proxy"; + + notifier_->AddPrefObserver(pref_name, &obs1_); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); + + // Re-adding the same observer for the same pref doesn't change anything. + // Skip this in debug mode, since it hits a DCHECK and death tests aren't + // thread-safe. +#if defined(NDEBUG) + notifier_->AddPrefObserver(pref_name, &obs1_); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); +#endif // NDEBUG + + // Ensure that we can add the same observer to a different pref. + notifier_->AddPrefObserver(pref_name2, &obs1_); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); + + // Ensure that we can add another observer to the same pref. + notifier_->AddPrefObserver(pref_name, &obs2_); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); + + // Ensure that we can remove all observers, and that removing a non-existent + // observer is harmless. + notifier_->RemovePrefObserver(pref_name, &obs1_); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); + + notifier_->RemovePrefObserver(pref_name, &obs2_); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); + + notifier_->RemovePrefObserver(pref_name, &obs1_); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(1u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); + + notifier_->RemovePrefObserver(pref_name2, &obs1_); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs1_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name, &obs2_)); + ASSERT_EQ(0u, notifier_->CountObserver(pref_name2, &obs2_)); +} + +TEST_F(PrefNotifierTest, FireObservers) { + using testing::_; + using testing::Field; + using testing::Invoke; + using testing::Mock; + using testing::Truly; + + // Tell googlemock to pass calls to the mock's "back door" too. + ON_CALL(*notifier_, FireObservers(_)) + .WillByDefault(Invoke(notifier_, &MockPrefNotifier::RealFireObservers)); + EXPECT_CALL(*notifier_, FireObservers(kChangedPref)).Times(4); + EXPECT_CALL(*notifier_, FireObservers(kUnchangedPref)).Times(0); + + notifier_->AddPrefObserver(kChangedPref, &obs1_); + notifier_->AddPrefObserver(kUnchangedPref, &obs1_); + + EXPECT_CALL(obs1_, Observe( + Field(&NotificationType::value, NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + Truly(DetailsAreChangedPref))); + EXPECT_CALL(obs2_, Observe(_, _, _)).Times(0); + notifier_->OnUserPreferenceSet(kChangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + EXPECT_CALL(obs1_, Observe(_, _, _)).Times(0); + EXPECT_CALL(obs2_, Observe(_, _, _)).Times(0); + notifier_->OnUserPreferenceSet(kUnchangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + notifier_->AddPrefObserver(kChangedPref, &obs2_); + notifier_->AddPrefObserver(kUnchangedPref, &obs2_); + + EXPECT_CALL(obs1_, Observe( + Field(&NotificationType::value, NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + Truly(DetailsAreChangedPref))); + EXPECT_CALL(obs2_, Observe( + Field(&NotificationType::value, NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + Truly(DetailsAreChangedPref))); + notifier_->OnUserPreferenceSet(kChangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + EXPECT_CALL(obs1_, Observe(_, _, _)).Times(0); + EXPECT_CALL(obs2_, Observe(_, _, _)).Times(0); + notifier_->OnUserPreferenceSet(kUnchangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + // Make sure removing an observer from one pref doesn't affect anything else. + notifier_->RemovePrefObserver(kChangedPref, &obs1_); + + EXPECT_CALL(obs1_, Observe(_, _, _)).Times(0); + EXPECT_CALL(obs2_, Observe( + Field(&NotificationType::value, NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + Truly(DetailsAreChangedPref))); + notifier_->OnUserPreferenceSet(kChangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + EXPECT_CALL(obs1_, Observe(_, _, _)).Times(0); + EXPECT_CALL(obs2_, Observe(_, _, _)).Times(0); + notifier_->OnUserPreferenceSet(kUnchangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + // Make sure removing an observer entirely doesn't affect anything else. + notifier_->RemovePrefObserver(kUnchangedPref, &obs1_); + + EXPECT_CALL(obs1_, Observe(_, _, _)).Times(0); + EXPECT_CALL(obs2_, Observe( + Field(&NotificationType::value, NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + Truly(DetailsAreChangedPref))); + notifier_->OnUserPreferenceSet(kChangedPref, NULL); + Mock::VerifyAndClearExpectations(&obs1_); + Mock::VerifyAndClearExpectations(&obs2_); + + EXPECT_CALL(obs1_, Observe(_, _, _)).Times(0); + EXPECT_CALL(obs2_, Observe(_, _, _)).Times(0); + notifier_->OnUserPreferenceSet(kUnchangedPref, NULL); + + notifier_->RemovePrefObserver(kChangedPref, &obs2_); + notifier_->RemovePrefObserver(kUnchangedPref, &obs2_); +} + +} // namespace diff --git a/chrome/browser/prefs/pref_service.cc b/chrome/browser/prefs/pref_service.cc new file mode 100644 index 0000000..1ab671a --- /dev/null +++ b/chrome/browser/prefs/pref_service.cc @@ -0,0 +1,737 @@ +// 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/prefs/pref_service.h" + +#include <algorithm> +#include <string> + +#include "app/l10n_util.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/histogram.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/stl_util-inl.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "build/build_config.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/profile.h" +#include "chrome/common/notification_service.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" + +namespace { + +// A helper function for RegisterLocalized*Pref that creates a Value* based on +// the string value in the locale dll. Because we control the values in a +// locale dll, this should always return a Value of the appropriate type. +Value* CreateLocaleDefaultValue(Value::ValueType type, int message_id) { + std::string resource_string = l10n_util::GetStringUTF8(message_id); + DCHECK(!resource_string.empty()); + switch (type) { + case Value::TYPE_BOOLEAN: { + if ("true" == resource_string) + return Value::CreateBooleanValue(true); + if ("false" == resource_string) + return Value::CreateBooleanValue(false); + break; + } + + case Value::TYPE_INTEGER: { + int val; + base::StringToInt(resource_string, &val); + return Value::CreateIntegerValue(val); + } + + case Value::TYPE_REAL: { + double val; + base::StringToDouble(resource_string, &val); + return Value::CreateRealValue(val); + } + + case Value::TYPE_STRING: { + return Value::CreateStringValue(resource_string); + } + + default: { + NOTREACHED() << + "list and dictionary types can not have default locale values"; + } + } + NOTREACHED(); + return Value::CreateNullValue(); +} + +// Forwards a notification after a PostMessage so that we can wait for the +// MessageLoop to run. +void NotifyReadError(PrefService* pref, int message_id) { + Source<PrefService> source(pref); + NotificationService::current()->Notify(NotificationType::PROFILE_ERROR, + source, Details<int>(&message_id)); +} + +} // namespace + +// static +PrefService* PrefService::CreatePrefService(const FilePath& pref_filename, + Profile* profile) { +#if defined(OS_LINUX) + // We'd like to see what fraction of our users have the preferences + // stored on a network file system, as we've had no end of troubles + // with NFS/AFS. + // TODO(evanm): remove this once we've collected state. + file_util::FileSystemType fstype; + if (file_util::GetFileSystemType(pref_filename.DirName(), &fstype)) { + UMA_HISTOGRAM_ENUMERATION("PrefService.FileSystemType", + static_cast<int>(fstype), + file_util::FILE_SYSTEM_TYPE_COUNT); + } +#endif + + return new PrefService( + PrefValueStore::CreatePrefValueStore(pref_filename, profile, false)); +} + +// static +PrefService* PrefService::CreateUserPrefService(const FilePath& pref_filename) { + return new PrefService( + PrefValueStore::CreatePrefValueStore(pref_filename, NULL, true)); +} + +PrefService::PrefService(PrefValueStore* pref_value_store) + : pref_value_store_(pref_value_store) { + pref_notifier_.reset(new PrefNotifier(this, pref_value_store)); + InitFromStorage(); +} + +PrefService::~PrefService() { + DCHECK(CalledOnValidThread()); + STLDeleteContainerPointers(prefs_.begin(), prefs_.end()); + prefs_.clear(); +} + +void PrefService::InitFromStorage() { + PrefStore::PrefReadError error = LoadPersistentPrefs(); + if (error == PrefStore::PREF_READ_ERROR_NONE) + return; + + // Failing to load prefs on startup is a bad thing(TM). See bug 38352 for + // an example problem that this can cause. + // Do some diagnosis and try to avoid losing data. + int message_id = 0; + if (error <= PrefStore::PREF_READ_ERROR_JSON_TYPE) { + message_id = IDS_PREFERENCES_CORRUPT_ERROR; + } else if (error != PrefStore::PREF_READ_ERROR_NO_FILE) { + message_id = IDS_PREFERENCES_UNREADABLE_ERROR; + } + + if (message_id) { + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableFunction(&NotifyReadError, this, message_id)); + } + UMA_HISTOGRAM_ENUMERATION("PrefService.ReadError", error, 20); +} + +bool PrefService::ReloadPersistentPrefs() { + return (LoadPersistentPrefs() == PrefStore::PREF_READ_ERROR_NONE); +} + +PrefStore::PrefReadError PrefService::LoadPersistentPrefs() { + DCHECK(CalledOnValidThread()); + + PrefStore::PrefReadError pref_error = pref_value_store_->ReadPrefs(); + + for (PreferenceSet::iterator it = prefs_.begin(); + it != prefs_.end(); ++it) { + (*it)->pref_value_store_ = pref_value_store_.get(); + } + + return pref_error; +} + +bool PrefService::SavePersistentPrefs() { + DCHECK(CalledOnValidThread()); + + return pref_value_store_->WritePrefs(); +} + +void PrefService::ScheduleSavePersistentPrefs() { + DCHECK(CalledOnValidThread()); + + pref_value_store_->ScheduleWritePrefs(); +} + +void PrefService::RegisterBooleanPref(const char* path, + bool default_value) { + Preference* pref = new Preference(pref_value_store_.get(), path, + Value::CreateBooleanValue(default_value)); + RegisterPreference(pref); +} + +void PrefService::RegisterIntegerPref(const char* path, int default_value) { + Preference* pref = new Preference(pref_value_store_.get(), path, + Value::CreateIntegerValue(default_value)); + RegisterPreference(pref); +} + +void PrefService::RegisterRealPref(const char* path, double default_value) { + Preference* pref = new Preference(pref_value_store_.get(), path, + Value::CreateRealValue(default_value)); + RegisterPreference(pref); +} + +void PrefService::RegisterStringPref(const char* path, + const std::string& default_value) { + Preference* pref = new Preference(pref_value_store_.get(), path, + Value::CreateStringValue(default_value)); + RegisterPreference(pref); +} + +void PrefService::RegisterFilePathPref(const char* path, + const FilePath& default_value) { + Preference* pref = new Preference(pref_value_store_.get(), path, + Value::CreateStringValue(default_value.value())); + RegisterPreference(pref); +} + +void PrefService::RegisterListPref(const char* path) { + Preference* pref = new Preference(pref_value_store_.get(), path, + new ListValue); + RegisterPreference(pref); +} + +void PrefService::RegisterDictionaryPref(const char* path) { + Preference* pref = new Preference(pref_value_store_.get(), path, + new DictionaryValue()); + RegisterPreference(pref); +} + +void PrefService::RegisterLocalizedBooleanPref(const char* path, + int locale_default_message_id) { + Preference* pref = new Preference(pref_value_store_.get(), path, + CreateLocaleDefaultValue(Value::TYPE_BOOLEAN, locale_default_message_id)); + RegisterPreference(pref); +} + +void PrefService::RegisterLocalizedIntegerPref(const char* path, + int locale_default_message_id) { + Preference* pref = new Preference(pref_value_store_.get(), path, + CreateLocaleDefaultValue(Value::TYPE_INTEGER, locale_default_message_id)); + RegisterPreference(pref); +} + +void PrefService::RegisterLocalizedRealPref(const char* path, + int locale_default_message_id) { + Preference* pref = new Preference(pref_value_store_.get(), path, + CreateLocaleDefaultValue(Value::TYPE_REAL, locale_default_message_id)); + RegisterPreference(pref); +} + +void PrefService::RegisterLocalizedStringPref(const char* path, + int locale_default_message_id) { + Preference* pref = new Preference(pref_value_store_.get(), path, + CreateLocaleDefaultValue(Value::TYPE_STRING, locale_default_message_id)); + RegisterPreference(pref); +} + +bool PrefService::GetBoolean(const char* path) const { + DCHECK(CalledOnValidThread()); + + bool result = false; + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = pref->GetValue()->GetAsBoolean(&result); + DCHECK(rv); + return result; +} + +int PrefService::GetInteger(const char* path) const { + DCHECK(CalledOnValidThread()); + + int result = 0; + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = pref->GetValue()->GetAsInteger(&result); + DCHECK(rv); + return result; +} + +double PrefService::GetReal(const char* path) const { + DCHECK(CalledOnValidThread()); + + double result = 0.0; + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = pref->GetValue()->GetAsReal(&result); + DCHECK(rv); + return result; +} + +std::string PrefService::GetString(const char* path) const { + DCHECK(CalledOnValidThread()); + + std::string result; + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return result; + } + bool rv = pref->GetValue()->GetAsString(&result); + DCHECK(rv); + return result; +} + +FilePath PrefService::GetFilePath(const char* path) const { + DCHECK(CalledOnValidThread()); + + FilePath::StringType result; + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return FilePath(result); + } + bool rv = pref->GetValue()->GetAsString(&result); + DCHECK(rv); +#if defined(OS_POSIX) + // We store filepaths as UTF8, so convert it back to the system type. + result = base::SysWideToNativeMB(UTF8ToWide(result)); +#endif + return FilePath(result); +} + +bool PrefService::HasPrefPath(const char* path) const { + return pref_value_store_->HasPrefPath(path); +} + +const PrefService::Preference* PrefService::FindPreference( + const char* pref_name) const { + DCHECK(CalledOnValidThread()); + Preference p(NULL, pref_name, NULL); + PreferenceSet::const_iterator it = prefs_.find(&p); + return it == prefs_.end() ? NULL : *it; +} + +bool PrefService::IsManagedPreference(const char* pref_name) const { + const Preference* pref = FindPreference(pref_name); + if (pref && pref->IsManaged()) { + return true; + } + return false; +} + +const DictionaryValue* PrefService::GetDictionary(const char* path) const { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return NULL; + } + const Value* value = pref->GetValue(); + if (value->GetType() == Value::TYPE_NULL) + return NULL; + return static_cast<const DictionaryValue*>(value); +} + +const ListValue* PrefService::GetList(const char* path) const { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return NULL; + } + const Value* value = pref->GetValue(); + if (value->GetType() == Value::TYPE_NULL) + return NULL; + return static_cast<const ListValue*>(value); +} + +void PrefService::AddPrefObserver(const char* path, + NotificationObserver* obs) { + pref_notifier_->AddPrefObserver(path, obs); +} + +void PrefService::RemovePrefObserver(const char* path, + NotificationObserver* obs) { + pref_notifier_->RemovePrefObserver(path, obs); +} + +void PrefService::RegisterPreference(Preference* pref) { + DCHECK(CalledOnValidThread()); + + if (FindPreference(pref->name().c_str())) { + NOTREACHED() << "Tried to register duplicate pref " << pref->name(); + delete pref; + return; + } + prefs_.insert(pref); +} + +void PrefService::ClearPref(const char* path) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to clear an unregistered pref: " << path; + return; + } + Value* value; + bool has_old_value = pref_value_store_->GetValue(path, &value); + pref_value_store_->RemoveUserPrefValue(path); + + // TODO(pamg): Removing the user value when there's a higher-priority setting + // doesn't change the value and shouldn't fire observers. + if (has_old_value) + pref_notifier_->FireObservers(path); +} + +void PrefService::Set(const char* path, const Value& value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + + // Allow dictionary and list types to be set to null. + if (value.GetType() == Value::TYPE_NULL && + (pref->type() == Value::TYPE_DICTIONARY || + pref->type() == Value::TYPE_LIST)) { + scoped_ptr<Value> old_value(GetPrefCopy(path)); + if (!old_value->Equals(&value)) { + pref_value_store_->RemoveUserPrefValue(path); + pref_notifier_->FireObservers(path); + } + return; + } + + if (pref->type() != value.GetType()) { + NOTREACHED() << "Wrong type for Set: " << path; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); + pref_value_store_->SetUserPrefValue(path, value.DeepCopy()); + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +void PrefService::SetBoolean(const char* path, bool value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->IsManaged()) { + NOTREACHED() << "Preference is managed: " << path; + return; + } + if (pref->type() != Value::TYPE_BOOLEAN) { + NOTREACHED() << "Wrong type for SetBoolean: " << path; + return; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); + Value* new_value = Value::CreateBooleanValue(value); + pref_value_store_->SetUserPrefValue(path, new_value); + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +void PrefService::SetInteger(const char* path, int value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->IsManaged()) { + NOTREACHED() << "Preference is managed: " << path; + return; + } + if (pref->type() != Value::TYPE_INTEGER) { + NOTREACHED() << "Wrong type for SetInteger: " << path; + return; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); + Value* new_value = Value::CreateIntegerValue(value); + pref_value_store_->SetUserPrefValue(path, new_value); + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +void PrefService::SetReal(const char* path, double value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->IsManaged()) { + NOTREACHED() << "Preference is managed: " << path; + return; + } + if (pref->type() != Value::TYPE_REAL) { + NOTREACHED() << "Wrong type for SetReal: " << path; + return; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); + Value* new_value = Value::CreateRealValue(value); + pref_value_store_->SetUserPrefValue(path, new_value); + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +void PrefService::SetString(const char* path, const std::string& value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->IsManaged()) { + NOTREACHED() << "Preference is managed: " << path; + return; + } + if (pref->type() != Value::TYPE_STRING) { + NOTREACHED() << "Wrong type for SetString: " << path; + return; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); + Value* new_value = Value::CreateStringValue(value); + pref_value_store_->SetUserPrefValue(path, new_value); + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +void PrefService::SetFilePath(const char* path, const FilePath& value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->IsManaged()) { + NOTREACHED() << "Preference is managed: " << path; + return; + } + if (pref->type() != Value::TYPE_STRING) { + NOTREACHED() << "Wrong type for SetFilePath: " << path; + return; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); +#if defined(OS_POSIX) + // Value::SetString only knows about UTF8 strings, so convert the path from + // the system native value to UTF8. + std::string path_utf8 = WideToUTF8(base::SysNativeMBToWide(value.value())); + Value* new_value = Value::CreateStringValue(path_utf8); + pref_value_store_->SetUserPrefValue(path, new_value); +#else + Value* new_value = Value::CreateStringValue(value.value()); + pref_value_store_->SetUserPrefValue(path, new_value); +#endif + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +void PrefService::SetInt64(const char* path, int64 value) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to write an unregistered pref: " << path; + return; + } + if (pref->IsManaged()) { + NOTREACHED() << "Preference is managed: " << path; + return; + } + if (pref->type() != Value::TYPE_STRING) { + NOTREACHED() << "Wrong type for SetInt64: " << path; + return; + } + + scoped_ptr<Value> old_value(GetPrefCopy(path)); + Value* new_value = Value::CreateStringValue(base::Int64ToString(value)); + pref_value_store_->SetUserPrefValue(path, new_value); + + pref_notifier_->OnUserPreferenceSet(path, old_value.get()); +} + +int64 PrefService::GetInt64(const char* path) const { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to read an unregistered pref: " << path; + return 0; + } + std::string result("0"); + bool rv = pref->GetValue()->GetAsString(&result); + DCHECK(rv); + + int64 val; + base::StringToInt64(result, &val); + return val; +} + +void PrefService::RegisterInt64Pref(const char* path, int64 default_value) { + Preference* pref = new Preference(pref_value_store_.get(), path, + Value::CreateStringValue(base::Int64ToString(default_value))); + RegisterPreference(pref); +} + +DictionaryValue* PrefService::GetMutableDictionary(const char* path) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to get an unregistered pref: " << path; + return NULL; + } + if (pref->type() != Value::TYPE_DICTIONARY) { + NOTREACHED() << "Wrong type for GetMutableDictionary: " << path; + return NULL; + } + + DictionaryValue* dict = NULL; + Value* tmp_value = NULL; + if (!pref_value_store_->GetValue(path, &tmp_value) || + !tmp_value->IsType(Value::TYPE_DICTIONARY)) { + dict = new DictionaryValue; + pref_value_store_->SetUserPrefValue(path, dict); + } else { + dict = static_cast<DictionaryValue*>(tmp_value); + } + return dict; +} + +ListValue* PrefService::GetMutableList(const char* path) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + if (!pref) { + NOTREACHED() << "Trying to get an unregistered pref: " << path; + return NULL; + } + if (pref->type() != Value::TYPE_LIST) { + NOTREACHED() << "Wrong type for GetMutableList: " << path; + return NULL; + } + + ListValue* list = NULL; + Value* tmp_value = NULL; + if (!pref_value_store_->GetValue(path, &tmp_value)) { + list = new ListValue; + pref_value_store_->SetUserPrefValue(path, list); + } else { + list = static_cast<ListValue*>(tmp_value); + } + return list; +} + +Value* PrefService::GetPrefCopy(const char* path) { + DCHECK(CalledOnValidThread()); + + const Preference* pref = FindPreference(path); + DCHECK(pref); + return pref->GetValue()->DeepCopy(); +} + +/////////////////////////////////////////////////////////////////////////////// +// PrefService::Preference + +PrefService::Preference::Preference(PrefValueStore* pref_value_store, + const char* name, + Value* default_value) + : type_(Value::TYPE_NULL), + name_(name), + default_value_(default_value), + pref_value_store_(pref_value_store) { + DCHECK(name); + + if (default_value) { + type_ = default_value->GetType(); + DCHECK(type_ != Value::TYPE_NULL && type_ != Value::TYPE_BINARY) << + "invalid preference type: " << type_; + } + + // We set the default value of lists and dictionaries to be null so it's + // easier for callers to check for empty list/dict prefs. + if (Value::TYPE_LIST == type_ || Value::TYPE_DICTIONARY == type_) + default_value_.reset(Value::CreateNullValue()); +} + +const Value* PrefService::Preference::GetValue() const { + DCHECK(NULL != pref_value_store_) << + "Must register pref before getting its value"; + + Value* temp_value = NULL; + if (pref_value_store_->GetValue(name_, &temp_value) && + temp_value->GetType() == type_) { + return temp_value; + } + + // Pref not found, just return the app default. + return default_value_.get(); +} + +bool PrefService::Preference::IsDefaultValue() const { + DCHECK(default_value_.get()); + return default_value_->Equals(GetValue()); +} + +bool PrefService::Preference::IsManaged() const { + return pref_value_store_->PrefValueInManagedStore(name_.c_str()); +} + +bool PrefService::Preference::HasExtensionSetting() const { + return pref_value_store_->PrefValueInExtensionStore(name_.c_str()); +} + +bool PrefService::Preference::HasUserSetting() const { + return pref_value_store_->PrefValueInUserStore(name_.c_str()); +} + +bool PrefService::Preference::IsExtensionControlled() const { + return pref_value_store_->PrefValueFromExtensionStore(name_.c_str()); +} + +bool PrefService::Preference::IsUserControlled() const { + return pref_value_store_->PrefValueFromUserStore(name_.c_str()); +} + +bool PrefService::Preference::IsUserModifiable() const { + return pref_value_store_->PrefValueUserModifiable(name_.c_str()); +} diff --git a/chrome/browser/prefs/pref_service.h b/chrome/browser/prefs/pref_service.h new file mode 100644 index 0000000..07fa5e5 --- /dev/null +++ b/chrome/browser/prefs/pref_service.h @@ -0,0 +1,254 @@ +// 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. + +// This provides a way to access the application's current preferences. + +#ifndef CHROME_BROWSER_PREFS_PREF_SERVICE_H_ +#define CHROME_BROWSER_PREFS_PREF_SERVICE_H_ +#pragma once + +#include <set> +#include <string> + +#include "base/non_thread_safe.h" +#include "base/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/common/pref_store.h" + +class FilePath; +class NotificationObserver; +class PrefNotifier; +class Profile; + +class PrefService : public NonThreadSafe { + public: + // A helper class to store all the information associated with a preference. + class Preference { + public: + + // The type of the preference is determined by the type of |default_value|. + // Therefore, the type needs to be a boolean, integer, real, string, + // dictionary (a branch), or list. You shouldn't need to construct this on + // your own, use the PrefService::Register*Pref methods instead. + // |default_value| will be owned by the Preference object. + Preference(PrefValueStore* pref_value_store, + const char* name, + Value* default_value); + ~Preference() {} + + Value::ValueType type() const { return type_; } + + // Returns the name of the Preference (i.e., the key, e.g., + // browser.window_placement). + const std::string name() const { return name_; } + + // Returns the value of the Preference. If there is no user specified + // value, it returns the default value. + const Value* GetValue() const; + + // Returns true if the current value matches the default value. + bool IsDefaultValue() const; + + // Returns true if the Preference is managed, i.e. set by an admin policy. + // Since managed prefs have the highest priority, this also indicates + // whether the pref is actually being controlled by the policy setting. + bool IsManaged() const; + + // Returns true if the Preference has a value set by an extension, even if + // that value is being overridden by a higher-priority source. + bool HasExtensionSetting() const; + + // Returns true if the Preference has a user setting, even if that value is + // being overridden by a higher-priority source. + bool HasUserSetting() const; + + // Returns true if the Preference value is currently being controlled by an + // extension, and not by any higher-priority source. + bool IsExtensionControlled() const; + + // Returns true if the Preference value is currently being controlled by a + // user setting, and not by any higher-priority source. + bool IsUserControlled() const; + + // Returns true if the user can change the Preference value, which is the + // case if no higher-priority source than the user store controls the + // Preference. + bool IsUserModifiable() const; + + private: + friend class PrefService; + + Value::ValueType type_; + std::string name_; + scoped_ptr<Value> default_value_; + + // A reference to the pref service's pref_value_store_. + PrefValueStore* pref_value_store_; + + DISALLOW_COPY_AND_ASSIGN(Preference); + }; + + // Factory method that creates a new instance of a PrefService with + // a PrefValueStore containing all platform-applicable PrefStores. + // The |pref_filename| points to the user preference file. The |profile| is + // the one to which these preferences apply; it may be NULL if we're dealing + // with the local state. This is the usual way to create a new PrefService. + static PrefService* CreatePrefService(const FilePath& pref_filename, + Profile* profile); + + // Convenience factory method for use in unit tests. Creates a new + // PrefService that uses a PrefValueStore with user preferences at the given + // |pref_filename|, and no other PrefStores (i.e., no other types of + // preferences). + static PrefService* CreateUserPrefService(const FilePath& pref_filename); + + // This constructor is primarily used by tests. The |pref_value_store| + // provides preference values. + explicit PrefService(PrefValueStore* pref_value_store); + + virtual ~PrefService(); + + // Reloads the data from file. This should only be called when the importer + // is running during first run, and the main process may not change pref + // values while the importer process is running. Returns true on success. + bool ReloadPersistentPrefs(); + + // Returns true if the preference for the given preference name is available + // and is managed. + bool IsManagedPreference(const char* pref_name) const; + + // Writes the data to disk. The return value only reflects whether + // serialization was successful; we don't know whether the data actually made + // it on disk (since it's on a different thread). This should only be used if + // we need to save immediately (basically, during shutdown). Otherwise, you + // should use ScheduleSavePersistentPrefs. + bool SavePersistentPrefs(); + + // Serializes the data and schedules save using ImportantFileWriter. + void ScheduleSavePersistentPrefs(); + + // Make the PrefService aware of a pref. + void RegisterBooleanPref(const char* path, bool default_value); + void RegisterIntegerPref(const char* path, int default_value); + void RegisterRealPref(const char* path, double default_value); + void RegisterStringPref(const char* path, const std::string& default_value); + void RegisterFilePathPref(const char* path, const FilePath& default_value); + void RegisterListPref(const char* path); + void RegisterDictionaryPref(const char* path); + + // These varients use a default value from the locale dll instead. + void RegisterLocalizedBooleanPref(const char* path, + int locale_default_message_id); + void RegisterLocalizedIntegerPref(const char* path, + int locale_default_message_id); + void RegisterLocalizedRealPref(const char* path, + int locale_default_message_id); + void RegisterLocalizedStringPref(const char* path, + int locale_default_message_id); + + // If the path is valid and the value at the end of the path matches the type + // specified, it will return the specified value. Otherwise, the default + // value (set when the pref was registered) will be returned. + bool GetBoolean(const char* path) const; + int GetInteger(const char* path) const; + double GetReal(const char* path) const; + std::string GetString(const char* path) const; + FilePath GetFilePath(const char* path) const; + + // Returns the branch if it exists. If it's not a branch or the branch does + // not exist, returns NULL. + const DictionaryValue* GetDictionary(const char* path) const; + const ListValue* GetList(const char* path) const; + + // If the pref at the given path changes, we call the observer's Observe + // method with NOTIFY_PREF_CHANGED. + virtual void AddPrefObserver(const char* path, NotificationObserver* obs); + void RemovePrefObserver(const char* path, NotificationObserver* obs); + + // Removes a user pref and restores the pref to its default value. + void ClearPref(const char* path); + + // If the path is valid (i.e., registered), update the pref value in the user + // prefs. + void Set(const char* path, const Value& value); + void SetBoolean(const char* path, bool value); + void SetInteger(const char* path, int value); + void SetReal(const char* path, double value); + void SetString(const char* path, const std::string& value); + void SetFilePath(const char* path, const FilePath& value); + + // Int64 helper methods that actually store the given value as a string. + // Note that if obtaining the named value via GetDictionary or GetList, the + // Value type will be TYPE_STRING. + void SetInt64(const char* path, int64 value); + int64 GetInt64(const char* path) const; + void RegisterInt64Pref(const char* path, int64 default_value); + + // Used to set the value of dictionary or list values in the pref tree. This + // will create a dictionary or list if one does not exist in the pref tree. + // This method returns NULL only if you're requesting an unregistered pref or + // a non-dict/non-list pref. + // WARNING: Changes to the dictionary or list will not automatically notify + // pref observers. + // Use a ScopedPrefUpdate to update observers on changes. + DictionaryValue* GetMutableDictionary(const char* path); + ListValue* GetMutableList(const char* path); + + // Returns true if a value has been set for the specified path. + // NOTE: this is NOT the same as FindPreference. In particular + // FindPreference returns whether RegisterXXX has been invoked, where as + // this checks if a value exists for the path. + bool HasPrefPath(const char* path) const; + + class PreferencePathComparator { + public: + bool operator() (Preference* lhs, Preference* rhs) const { + return lhs->name() < rhs->name(); + } + }; + typedef std::set<Preference*, PreferencePathComparator> PreferenceSet; + const PreferenceSet& preference_set() const { return prefs_; } + + // A helper method to quickly look up a preference. Returns NULL if the + // preference is not registered. + const Preference* FindPreference(const char* pref_name) const; + + bool read_only() const { return pref_value_store_->ReadOnly(); } + + PrefNotifier* pref_notifier() const { return pref_notifier_.get(); } + + protected: + // The PrefNotifier handles registering and notifying preference observers. + // It is created and owned by this PrefService. Subclasses may access it for + // unit testing. + scoped_ptr<PrefNotifier> pref_notifier_; + + private: + // Add a preference to the PreferenceMap. If the pref already exists, return + // false. This method takes ownership of |pref|. + void RegisterPreference(Preference* pref); + + // Returns a copy of the current pref value. The caller is responsible for + // deleting the returned object. + Value* GetPrefCopy(const char* pref_name); + + // Load from disk. Returns a non-zero error code on failure. + PrefStore::PrefReadError LoadPersistentPrefs(); + + // Load preferences from storage, attempting to diagnose and handle errors. + // This should only be called from the constructor. + void InitFromStorage(); + + // The PrefValueStore provides prioritized preference values. It is created + // and owned by this PrefService. Subclasses may access it for unit testing. + scoped_refptr<PrefValueStore> pref_value_store_; + + // A set of all the registered Preference objects. + PreferenceSet prefs_; + + DISALLOW_COPY_AND_ASSIGN(PrefService); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_SERVICE_H_ diff --git a/chrome/browser/prefs/pref_service_uitest.cc b/chrome/browser/prefs/pref_service_uitest.cc new file mode 100644 index 0000000..9712481 --- /dev/null +++ b/chrome/browser/prefs/pref_service_uitest.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2009 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 <string> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/test/test_file_util.h" +#include "base/values.h" +#include "build/build_config.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/json_value_serializer.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/window_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "gfx/rect.h" + +class PreferenceServiceTest : public UITest { + public: + void SetUp() { + PathService::Get(base::DIR_TEMP, &tmp_profile_); + tmp_profile_ = tmp_profile_.AppendASCII("tmp_profile"); + + // Create a fresh, empty copy of this directory. + file_util::Delete(tmp_profile_, true); + file_util::CreateDirectory(tmp_profile_); + + FilePath reference_pref_file = + test_data_directory_ + .AppendASCII("profiles") + .AppendASCII("window_placement") + .Append(chrome::kLocalStateFilename); + + tmp_pref_file_ = tmp_profile_.Append(chrome::kLocalStateFilename); + + ASSERT_TRUE(file_util::PathExists(reference_pref_file)); + + // Copy only the Local State file, the rest will be automatically created + ASSERT_TRUE(file_util::CopyFile(reference_pref_file, tmp_pref_file_)); + +#if defined(OS_WIN) + // Make the copy writable. On POSIX we assume the umask allows files + // we create to be writable. + ASSERT_TRUE(::SetFileAttributesW(tmp_pref_file_.value().c_str(), + FILE_ATTRIBUTE_NORMAL)); +#endif + + launch_arguments_.AppendSwitchPath(switches::kUserDataDir, tmp_profile_); + } + + bool LaunchAppWithProfile() { + if (!file_util::PathExists(tmp_pref_file_)) + return false; + UITest::SetUp(); + return true; + } + + void TearDown() { + UITest::TearDown(); + + EXPECT_TRUE(file_util::DieFileDie(tmp_profile_, true)); + } + + public: + FilePath tmp_pref_file_; + FilePath tmp_profile_; +}; + +#if defined(OS_WIN) +// This test verifies that the window position from the prefs file is restored +// when the app restores. This doesn't really make sense on Linux, where +// the window manager might fight with you over positioning. However, we +// might be able to make this work on buildbots. +// Also, not sure what should happen on the mac. In any case, the code below +// (minus the Windows bits) compiles fine on my Linux box now. +// TODO(port): revisit this. +TEST_F(PreferenceServiceTest, PreservedWindowPlacementIsLoaded) { + // The window should open with the reference profile + ASSERT_TRUE(LaunchAppWithProfile()); + + ASSERT_TRUE(file_util::PathExists(tmp_pref_file_)); + + JSONFileValueSerializer deserializer(tmp_pref_file_); + scoped_ptr<Value> root(deserializer.Deserialize(NULL, NULL)); + + ASSERT_TRUE(root.get()); + ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + + DictionaryValue* root_dict = static_cast<DictionaryValue*>(root.get()); + + // Retrieve the screen rect for the launched window + scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + scoped_refptr<WindowProxy> window(browser->GetWindow()); + ASSERT_TRUE(window.get()); + + gfx::Rect bounds; + ASSERT_TRUE(window->GetBounds(&bounds)); + + // Retrieve the expected rect values from "Preferences" + int bottom = 0; + std::string kBrowserWindowPlacement(prefs::kBrowserWindowPlacement); + EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".bottom", + &bottom)); + EXPECT_EQ(bottom, bounds.y() + bounds.height()); + + int top = 0; + EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".top", + &top)); + EXPECT_EQ(top, bounds.y()); + + int left = 0; + EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".left", + &left)); + EXPECT_EQ(left, bounds.x()); + + int right = 0; + EXPECT_TRUE(root_dict->GetInteger(kBrowserWindowPlacement + ".right", + &right)); + EXPECT_EQ(right, bounds.x() + bounds.width()); + + // Find if launched window is maximized. + bool is_window_maximized = false; + ASSERT_TRUE(window->IsMaximized(&is_window_maximized)); + bool is_maximized = false; + EXPECT_TRUE(root_dict->GetBoolean(kBrowserWindowPlacement + ".maximized", + &is_maximized)); + EXPECT_EQ(is_maximized, is_window_maximized); +} +#endif diff --git a/chrome/browser/prefs/pref_service_unittest.cc b/chrome/browser/prefs/pref_service_unittest.cc new file mode 100644 index 0000000..b6dd531 --- /dev/null +++ b/chrome/browser/prefs/pref_service_unittest.cc @@ -0,0 +1,289 @@ +// 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 <string> + +#include "app/test/data/resource.h" +#include "base/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/prefs/dummy_pref_store.h" +#include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/notification_observer_mock.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Mock; +using testing::Pointee; +using testing::Property; + +class TestPrefObserver : public NotificationObserver { + public: + TestPrefObserver(const PrefService* prefs, + const std::string& pref_name, + const std::string& new_pref_value) + : observer_fired_(false), + prefs_(prefs), + pref_name_(pref_name), + new_pref_value_(new_pref_value) { + } + virtual ~TestPrefObserver() {} + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + EXPECT_EQ(type.value, NotificationType::PREF_CHANGED); + PrefService* prefs_in = Source<PrefService>(source).ptr(); + EXPECT_EQ(prefs_in, prefs_); + std::string* pref_name_in = Details<std::string>(details).ptr(); + EXPECT_EQ(*pref_name_in, pref_name_); + EXPECT_EQ(new_pref_value_, prefs_in->GetString("homepage")); + observer_fired_ = true; + } + + bool observer_fired() { return observer_fired_; } + + void Reset(const std::string& new_pref_value) { + observer_fired_ = false; + new_pref_value_ = new_pref_value; + } + + private: + bool observer_fired_; + const PrefService* prefs_; + const std::string pref_name_; + std::string new_pref_value_; +}; + +// TODO(port): port this test to POSIX. +#if defined(OS_WIN) +TEST(PrefServiceTest, LocalizedPrefs) { + TestingPrefService prefs; + const char kBoolean[] = "boolean"; + const char kInteger[] = "integer"; + const char kString[] = "string"; + prefs.RegisterLocalizedBooleanPref(kBoolean, IDS_LOCALE_BOOL); + prefs.RegisterLocalizedIntegerPref(kInteger, IDS_LOCALE_INT); + prefs.RegisterLocalizedStringPref(kString, IDS_LOCALE_STRING); + + // The locale default should take preference over the user default. + EXPECT_FALSE(prefs.GetBoolean(kBoolean)); + EXPECT_EQ(1, prefs.GetInteger(kInteger)); + EXPECT_EQ("hello", prefs.GetString(kString)); + + prefs.SetBoolean(kBoolean, true); + EXPECT_TRUE(prefs.GetBoolean(kBoolean)); + prefs.SetInteger(kInteger, 5); + EXPECT_EQ(5, prefs.GetInteger(kInteger)); + prefs.SetString(kString, "foo"); + EXPECT_EQ("foo", prefs.GetString(kString)); +} +#endif + +TEST(PrefServiceTest, NoObserverFire) { + TestingPrefService prefs; + + const char pref_name[] = "homepage"; + prefs.RegisterStringPref(pref_name, ""); + + const std::string new_pref_value("http://www.google.com/"); + TestPrefObserver obs(&prefs, pref_name, new_pref_value); + prefs.AddPrefObserver(pref_name, &obs); + // This should fire the checks in TestPrefObserver::Observe. + prefs.SetString(pref_name, new_pref_value); + + // Make sure the observer was actually fired. + EXPECT_TRUE(obs.observer_fired()); + + // Setting the pref to the same value should not set the pref value a second + // time. + obs.Reset(new_pref_value); + prefs.SetString(pref_name, new_pref_value); + EXPECT_FALSE(obs.observer_fired()); + + // Clearing the pref should cause the pref to fire. + obs.Reset(""); + prefs.ClearPref(pref_name); + EXPECT_TRUE(obs.observer_fired()); + + // Clearing the pref again should not cause the pref to fire. + obs.Reset(""); + prefs.ClearPref(pref_name); + EXPECT_FALSE(obs.observer_fired()); + + // Ok, clean up. + prefs.RemovePrefObserver(pref_name, &obs); +} + +TEST(PrefServiceTest, HasPrefPath) { + TestingPrefService prefs; + + const char path[] = "fake.path"; + + // Shouldn't initially have a path. + EXPECT_FALSE(prefs.HasPrefPath(path)); + + // Register the path. This doesn't set a value, so the path still shouldn't + // exist. + prefs.RegisterStringPref(path, std::string()); + EXPECT_FALSE(prefs.HasPrefPath(path)); + + // Set a value and make sure we have a path. + prefs.SetString(path, "blah"); + EXPECT_TRUE(prefs.HasPrefPath(path)); +} + +TEST(PrefServiceTest, Observers) { + const char pref_name[] = "homepage"; + + TestingPrefService prefs; + prefs.SetUserPref(pref_name, Value::CreateStringValue("http://www.cnn.com")); + prefs.RegisterStringPref(pref_name, ""); + + const std::string new_pref_value("http://www.google.com/"); + TestPrefObserver obs(&prefs, pref_name, new_pref_value); + prefs.AddPrefObserver(pref_name, &obs); + // This should fire the checks in TestPrefObserver::Observe. + prefs.SetString(pref_name, new_pref_value); + + // Make sure the tests were actually run. + EXPECT_TRUE(obs.observer_fired()); + + // Now try adding a second pref observer. + const std::string new_pref_value2("http://www.youtube.com/"); + obs.Reset(new_pref_value2); + TestPrefObserver obs2(&prefs, pref_name, new_pref_value2); + prefs.AddPrefObserver(pref_name, &obs2); + // This should fire the checks in obs and obs2. + prefs.SetString(pref_name, new_pref_value2); + EXPECT_TRUE(obs.observer_fired()); + EXPECT_TRUE(obs2.observer_fired()); + + // Make sure obs2 still works after removing obs. + prefs.RemovePrefObserver(pref_name, &obs); + obs.Reset(""); + obs2.Reset(new_pref_value); + // This should only fire the observer in obs2. + prefs.SetString(pref_name, new_pref_value); + EXPECT_FALSE(obs.observer_fired()); + EXPECT_TRUE(obs2.observer_fired()); + + // Ok, clean up. + prefs.RemovePrefObserver(pref_name, &obs2); +} + +class PrefServiceSetValueTest : public testing::Test { + protected: + static const char name_[]; + static const char value_[]; + + PrefServiceSetValueTest() + : name_string_(name_), + null_value_(Value::CreateNullValue()) {} + + void SetExpectNoNotification() { + EXPECT_CALL(observer_, Observe(_, _, _)).Times(0); + } + + void SetExpectPrefChanged() { + EXPECT_CALL(observer_, + Observe(NotificationType(NotificationType::PREF_CHANGED), _, + Property(&Details<std::string>::ptr, + Pointee(name_string_)))); + } + + TestingPrefService prefs_; + std::string name_string_; + scoped_ptr<Value> null_value_; + NotificationObserverMock observer_; +}; + +const char PrefServiceSetValueTest::name_[] = "name"; +const char PrefServiceSetValueTest::value_[] = "value"; + +TEST_F(PrefServiceSetValueTest, SetStringValue) { + const char default_string[] = "default"; + scoped_ptr<Value> default_value(Value::CreateStringValue(default_string)); + prefs_.RegisterStringPref(name_, default_string); + prefs_.AddPrefObserver(name_, &observer_); + SetExpectNoNotification(); + prefs_.Set(name_, *default_value); + Mock::VerifyAndClearExpectations(&observer_); + + scoped_ptr<Value> new_value(Value::CreateStringValue(value_)); + SetExpectPrefChanged(); + prefs_.Set(name_, *new_value); + EXPECT_EQ(value_, prefs_.GetString(name_)); + + prefs_.RemovePrefObserver(name_, &observer_); +} + +TEST_F(PrefServiceSetValueTest, SetDictionaryValue) { + prefs_.RegisterDictionaryPref(name_); + prefs_.AddPrefObserver(name_, &observer_); + + SetExpectNoNotification(); + prefs_.Set(name_, *null_value_); + Mock::VerifyAndClearExpectations(&observer_); + + DictionaryValue new_value; + new_value.SetString(name_, value_); + SetExpectPrefChanged(); + prefs_.Set(name_, new_value); + Mock::VerifyAndClearExpectations(&observer_); + DictionaryValue* dict = prefs_.GetMutableDictionary(name_); + EXPECT_EQ(1U, dict->size()); + std::string out_value; + dict->GetString(name_, &out_value); + EXPECT_EQ(value_, out_value); + + SetExpectNoNotification(); + prefs_.Set(name_, new_value); + Mock::VerifyAndClearExpectations(&observer_); + + SetExpectPrefChanged(); + prefs_.Set(name_, *null_value_); + Mock::VerifyAndClearExpectations(&observer_); + dict = prefs_.GetMutableDictionary(name_); + EXPECT_EQ(0U, dict->size()); + + prefs_.RemovePrefObserver(name_, &observer_); +} + +TEST_F(PrefServiceSetValueTest, SetListValue) { + prefs_.RegisterListPref(name_); + prefs_.AddPrefObserver(name_, &observer_); + + SetExpectNoNotification(); + prefs_.Set(name_, *null_value_); + Mock::VerifyAndClearExpectations(&observer_); + + ListValue new_value; + new_value.Append(Value::CreateStringValue(value_)); + SetExpectPrefChanged(); + prefs_.Set(name_, new_value); + Mock::VerifyAndClearExpectations(&observer_); + ListValue* list = prefs_.GetMutableList(name_); + ASSERT_EQ(1U, list->GetSize()); + std::string out_value; + list->GetString(0, &out_value); + EXPECT_EQ(value_, out_value); + + SetExpectNoNotification(); + prefs_.Set(name_, new_value); + Mock::VerifyAndClearExpectations(&observer_); + + SetExpectPrefChanged(); + prefs_.Set(name_, *null_value_); + Mock::VerifyAndClearExpectations(&observer_); + list = prefs_.GetMutableList(name_); + EXPECT_EQ(0U, list->GetSize()); + + prefs_.RemovePrefObserver(name_, &observer_); +} diff --git a/chrome/browser/prefs/pref_set_observer.cc b/chrome/browser/prefs/pref_set_observer.cc new file mode 100644 index 0000000..0f41500 --- /dev/null +++ b/chrome/browser/prefs/pref_set_observer.cc @@ -0,0 +1,66 @@ +// 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/prefs/pref_set_observer.h" + +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" + +PrefSetObserver::PrefSetObserver(PrefService* pref_service, + NotificationObserver* observer) + : pref_service_(pref_service), + observer_(observer) { +} + +PrefSetObserver::~PrefSetObserver() { + for (PrefSet::const_iterator i(prefs_.begin()); i != prefs_.end(); ++i) + pref_service_->RemovePrefObserver(i->c_str(), this); +} + +void PrefSetObserver::AddPref(const std::string& pref) { + if (!prefs_.count(pref) && pref_service_->FindPreference(pref.c_str())) { + prefs_.insert(pref); + pref_service_->AddPrefObserver(pref.c_str(), this); + } +} + +void PrefSetObserver::RemovePref(const std::string& pref) { + if (prefs_.erase(pref)) + pref_service_->RemovePrefObserver(pref.c_str(), this); +} + +bool PrefSetObserver::IsObserved(const std::string& pref) { + return prefs_.count(pref) > 0; +} + +bool PrefSetObserver::IsManaged() { + for (PrefSet::const_iterator i(prefs_.begin()); i != prefs_.end(); ++i) { + const PrefService::Preference* pref = + pref_service_->FindPreference(i->c_str()); + if (pref && pref->IsManaged()) + return true; + } + return false; +} + +// static +PrefSetObserver* PrefSetObserver::CreateProxyPrefSetObserver( + PrefService* pref_service, + NotificationObserver* observer) { + PrefSetObserver* pref_set = new PrefSetObserver(pref_service, observer); + pref_set->AddPref(prefs::kNoProxyServer); + pref_set->AddPref(prefs::kProxyAutoDetect); + pref_set->AddPref(prefs::kProxyServer); + pref_set->AddPref(prefs::kProxyPacUrl); + pref_set->AddPref(prefs::kProxyBypassList); + + return pref_set; +} + +void PrefSetObserver::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (observer_) + observer_->Observe(type, source, details); +} diff --git a/chrome/browser/prefs/pref_set_observer.h b/chrome/browser/prefs/pref_set_observer.h new file mode 100644 index 0000000..038a3f5 --- /dev/null +++ b/chrome/browser/prefs/pref_set_observer.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef CHROME_BROWSER_PREFS_PREF_SET_OBSERVER_H_ +#define CHROME_BROWSER_PREFS_PREF_SET_OBSERVER_H_ +#pragma once + +#include <set> + +#include "base/basictypes.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/notification_observer.h" + +// Observes the state of a set of preferences and allows to query their combined +// managed bits. +class PrefSetObserver : public NotificationObserver { + public: + // Initialize with an empty set of preferences. + PrefSetObserver(PrefService* pref_service, + NotificationObserver* observer); + virtual ~PrefSetObserver(); + + // Add a |pref| to the set of preferences to observe. + void AddPref(const std::string& pref); + // Remove |pref| from the set of observed peferences. + void RemovePref(const std::string& pref); + + // Check whether |pref| is in the set of observed preferences. + bool IsObserved(const std::string& pref); + // Check whether any of the observed preferences has the managed bit set. + bool IsManaged(); + + // Create a pref set observer for all preferences relavant to proxies. + static PrefSetObserver* CreateProxyPrefSetObserver( + PrefService* pref_service, + NotificationObserver* observer); + + private: + // Overridden from NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + typedef std::set<std::string> PrefSet; + PrefSet prefs_; + + PrefService* pref_service_; + NotificationObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(PrefSetObserver); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_SET_OBSERVER_H_ diff --git a/chrome/browser/prefs/pref_set_observer_unittest.cc b/chrome/browser/prefs/pref_set_observer_unittest.cc new file mode 100644 index 0000000..f77bb6c --- /dev/null +++ b/chrome/browser/prefs/pref_set_observer_unittest.cc @@ -0,0 +1,89 @@ +// 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/prefs/pref_set_observer.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_observer_mock.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Unit tests for PrefSetObserver. +class PrefSetObserverTest : public testing::Test { + public: + virtual void SetUp() { + pref_service_.reset(new TestingPrefService); + pref_service_->RegisterStringPref(prefs::kHomePage, "http://google.com"); + pref_service_->RegisterBooleanPref(prefs::kHomePageIsNewTabPage, false); + pref_service_->RegisterStringPref(prefs::kApplicationLocale, ""); + } + + PrefSetObserver* CreatePrefSetObserver(NotificationObserver* observer) { + PrefSetObserver* pref_set = + new PrefSetObserver(pref_service_.get(), observer); + pref_set->AddPref(prefs::kHomePage); + pref_set->AddPref(prefs::kHomePageIsNewTabPage); + return pref_set; + } + + scoped_ptr<TestingPrefService> pref_service_; +}; + +TEST_F(PrefSetObserverTest, IsObserved) { + scoped_ptr<PrefSetObserver> pref_set(CreatePrefSetObserver(NULL)); + EXPECT_TRUE(pref_set->IsObserved(prefs::kHomePage)); + EXPECT_TRUE(pref_set->IsObserved(prefs::kHomePageIsNewTabPage)); + EXPECT_FALSE(pref_set->IsObserved(prefs::kApplicationLocale)); +} + +TEST_F(PrefSetObserverTest, IsManaged) { + scoped_ptr<PrefSetObserver> pref_set(CreatePrefSetObserver(NULL)); + EXPECT_FALSE(pref_set->IsManaged()); + pref_service_->SetManagedPref(prefs::kHomePage, + Value::CreateStringValue("http://crbug.com")); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->SetManagedPref(prefs::kHomePageIsNewTabPage, + Value::CreateBooleanValue(true)); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->RemoveManagedPref(prefs::kHomePage); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->RemoveManagedPref(prefs::kHomePageIsNewTabPage); + EXPECT_FALSE(pref_set->IsManaged()); +} + +MATCHER_P(PrefNameDetails, name, "details references named preference") { + std::string* pstr = reinterpret_cast<const Details<std::string>&>(arg).ptr(); + return pstr && *pstr == name; +} + +TEST_F(PrefSetObserverTest, Observe) { + using testing::_; + using testing::Mock; + + NotificationObserverMock observer; + scoped_ptr<PrefSetObserver> pref_set(CreatePrefSetObserver(&observer)); + + EXPECT_CALL(observer, + Observe(NotificationType(NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + PrefNameDetails(prefs::kHomePage))); + pref_service_->SetUserPref(prefs::kHomePage, + Value::CreateStringValue("http://crbug.com")); + Mock::VerifyAndClearExpectations(&observer); + + EXPECT_CALL(observer, + Observe(NotificationType(NotificationType::PREF_CHANGED), + Source<PrefService>(pref_service_.get()), + PrefNameDetails(prefs::kHomePageIsNewTabPage))); + pref_service_->SetUserPref(prefs::kHomePageIsNewTabPage, + Value::CreateBooleanValue(true)); + Mock::VerifyAndClearExpectations(&observer); + + EXPECT_CALL(observer, Observe(_, _, _)).Times(0); + pref_service_->SetUserPref(prefs::kApplicationLocale, + Value::CreateStringValue("en_US.utf8")); + Mock::VerifyAndClearExpectations(&observer); +} diff --git a/chrome/browser/prefs/pref_value_store.cc b/chrome/browser/prefs/pref_value_store.cc new file mode 100644 index 0000000..6ab4b8f --- /dev/null +++ b/chrome/browser/prefs/pref_value_store.cc @@ -0,0 +1,299 @@ +// 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/prefs/pref_value_store.h" + +#include "base/values.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_pref_store.h" +#include "chrome/browser/policy/configuration_policy_pref_store.h" +#include "chrome/browser/prefs/command_line_pref_store.h" +#include "chrome/common/json_pref_store.h" +#include "chrome/common/notification_service.h" + +// static +PrefValueStore* PrefValueStore::CreatePrefValueStore( + const FilePath& pref_filename, + Profile* profile, + bool user_only) { + ConfigurationPolicyPrefStore* managed = NULL; + ExtensionPrefStore* extension = NULL; + CommandLinePrefStore* command_line = NULL; + JsonPrefStore* user = NULL; + ConfigurationPolicyPrefStore* recommended = NULL; + + if (!pref_filename.empty()) { + user = new JsonPrefStore( + pref_filename, + ChromeThread::GetMessageLoopProxyForThread(ChromeThread::FILE)); + } + + if (!user_only) { + managed = ConfigurationPolicyPrefStore::CreateManagedPolicyPrefStore(); + extension = new ExtensionPrefStore(profile, PrefNotifier::EXTENSION_STORE); + command_line = new CommandLinePrefStore(CommandLine::ForCurrentProcess()); + recommended = + ConfigurationPolicyPrefStore::CreateRecommendedPolicyPrefStore(); + } + return new PrefValueStore(managed, extension, command_line, user, + recommended); +} + +PrefValueStore::~PrefValueStore() {} + +bool PrefValueStore::GetValue(const std::string& name, + Value** out_value) const { + // Check the |PrefStore|s in order of their priority from highest to lowest + // to find the value of the preference described by the given preference name. + for (size_t i = 0; i <= PrefNotifier::PREF_STORE_TYPE_MAX; ++i) { + if (pref_stores_[i].get() && + pref_stores_[i]->prefs()->Get(name.c_str(), out_value)) { + return true; + } + } + // No value found for the given preference name, set the return false. + *out_value = NULL; + return false; +} + +bool PrefValueStore::WritePrefs() { + bool success = true; + for (size_t i = 0; i <= PrefNotifier::PREF_STORE_TYPE_MAX; ++i) { + if (pref_stores_[i].get()) + success = pref_stores_[i]->WritePrefs() && success; + } + return success; +} + +void PrefValueStore::ScheduleWritePrefs() { + for (size_t i = 0; i <= PrefNotifier::PREF_STORE_TYPE_MAX; ++i) { + if (pref_stores_[i].get()) + pref_stores_[i]->ScheduleWritePrefs(); + } +} + +PrefStore::PrefReadError PrefValueStore::ReadPrefs() { + PrefStore::PrefReadError result = PrefStore::PREF_READ_ERROR_NONE; + for (size_t i = 0; i <= PrefNotifier::PREF_STORE_TYPE_MAX; ++i) { + if (pref_stores_[i].get()) { + PrefStore::PrefReadError this_error = pref_stores_[i]->ReadPrefs(); + if (result == PrefStore::PREF_READ_ERROR_NONE) + result = this_error; + } + } + // TODO(markusheintz): Return a better error status: maybe a struct with + // the error status of all PrefStores. + return result; +} + +bool PrefValueStore::HasPrefPath(const char* path) const { + Value* tmp_value = NULL; + const std::string name(path); + bool rv = GetValue(name, &tmp_value); + return rv; +} + +bool PrefValueStore::PrefHasChanged(const char* path, + PrefNotifier::PrefStoreType new_store, + const Value* old_value) { + DCHECK(new_store != PrefNotifier::INVALID_STORE); + // Replying that the pref has changed may cause extra work, but it should + // not be actively harmful. + if (new_store == PrefNotifier::INVALID_STORE) + return true; + + Value* new_value = NULL; + GetValue(path, &new_value); + // Some unit tests have no values for certain prefs. + if (!new_value || !old_value->Equals(new_value)) + return true; + + // If there's a value in a store with lower priority than the |new_store|, + // and no value in a store with higher priority, assume the |new_store| just + // took control of the pref. (This assumption is wrong if the new value + // and store are both the same as the old one, but that situation should be + // rare, and reporting a change when none happened should not be harmful.) + if (PrefValueInStoreRange(path, new_store, false) && + !PrefValueInStoreRange(path, new_store, true)) + return true; + + return false; +} + +// Note the |DictionaryValue| referenced by the |PrefStore| user_prefs_ +// (returned by the method prefs()) takes the ownership of the Value referenced +// by in_value. +void PrefValueStore::SetUserPrefValue(const char* name, Value* in_value) { + pref_stores_[PrefNotifier::USER_STORE]->prefs()->Set(name, in_value); +} + +bool PrefValueStore::ReadOnly() { + return pref_stores_[PrefNotifier::USER_STORE]->ReadOnly(); +} + +void PrefValueStore::RemoveUserPrefValue(const char* name) { + if (pref_stores_[PrefNotifier::USER_STORE].get()) { + pref_stores_[PrefNotifier::USER_STORE]->prefs()->Remove(name, NULL); + } +} + +bool PrefValueStore::PrefValueInManagedStore(const char* name) { + return PrefValueInStore(name, PrefNotifier::MANAGED_STORE); +} + +bool PrefValueStore::PrefValueInExtensionStore(const char* name) { + return PrefValueInStore(name, PrefNotifier::EXTENSION_STORE); +} + +bool PrefValueStore::PrefValueInUserStore(const char* name) { + return PrefValueInStore(name, PrefNotifier::USER_STORE); +} + +bool PrefValueStore::PrefValueFromExtensionStore(const char* name) { + return ControllingPrefStoreForPref(name) == PrefNotifier::EXTENSION_STORE; +} + +bool PrefValueStore::PrefValueFromUserStore(const char* name) { + return ControllingPrefStoreForPref(name) == PrefNotifier::USER_STORE; +} + +bool PrefValueStore::PrefValueUserModifiable(const char* name) { + PrefNotifier::PrefStoreType effective_store = + ControllingPrefStoreForPref(name); + return effective_store >= PrefNotifier::USER_STORE || + effective_store == PrefNotifier::INVALID_STORE; +} + +bool PrefValueStore::PrefValueInStore(const char* name, + PrefNotifier::PrefStoreType type) { + if (!pref_stores_[type].get()) { + // No store of that type set, so this pref can't be in it. + return false; + } + Value* tmp_value; + return pref_stores_[type]->prefs()->Get(name, &tmp_value); +} + +bool PrefValueStore::PrefValueInStoreRange(const char* name, + PrefNotifier::PrefStoreType boundary, + bool higher_priority) { + // Higher priorities are lower PrefStoreType values. The range is + // non-inclusive of the boundary. + int start = higher_priority ? 0 : boundary + 1; + int end = higher_priority ? boundary - 1 : + PrefNotifier::PREF_STORE_TYPE_MAX; + + for (int i = start; i <= end ; ++i) { + if (PrefValueInStore(name, static_cast<PrefNotifier::PrefStoreType>(i))) + return true; + } + return false; +} + + +PrefNotifier::PrefStoreType PrefValueStore::ControllingPrefStoreForPref( + const char* name) { + for (int i = 0; i <= PrefNotifier::PREF_STORE_TYPE_MAX; ++i) { + if (PrefValueInStore(name, static_cast<PrefNotifier::PrefStoreType>(i))) + return static_cast<PrefNotifier::PrefStoreType>(i); + } + return PrefNotifier::INVALID_STORE; +} + +void PrefValueStore::RefreshPolicyPrefsCompletion( + PrefStore* new_managed_pref_store, + PrefStore* new_recommended_pref_store, + AfterRefreshCallback* callback_pointer) { + scoped_ptr<AfterRefreshCallback> callback(callback_pointer); + DictionaryValue* managed_prefs_before( + pref_stores_[PrefNotifier::MANAGED_STORE]->prefs()); + DictionaryValue* managed_prefs_after(new_managed_pref_store->prefs()); + DictionaryValue* recommended_prefs_before( + pref_stores_[PrefNotifier::RECOMMENDED_STORE]->prefs()); + DictionaryValue* recommended_prefs_after(new_recommended_pref_store->prefs()); + + std::vector<std::string> changed_managed_paths; + managed_prefs_before->GetDifferingPaths(managed_prefs_after, + &changed_managed_paths); + + std::vector<std::string> changed_recommended_paths; + recommended_prefs_before->GetDifferingPaths(recommended_prefs_after, + &changed_recommended_paths); + + std::vector<std::string> changed_paths(changed_managed_paths.size() + + changed_recommended_paths.size()); + std::vector<std::string>::iterator last_insert = + std::merge(changed_managed_paths.begin(), + changed_managed_paths.end(), + changed_recommended_paths.begin(), + changed_recommended_paths.end(), + changed_paths.begin()); + changed_paths.resize(last_insert - changed_paths.begin()); + + pref_stores_[PrefNotifier::MANAGED_STORE].reset(new_managed_pref_store); + pref_stores_[PrefNotifier::RECOMMENDED_STORE].reset( + new_recommended_pref_store); + callback->Run(changed_paths); +} + +void PrefValueStore::RefreshPolicyPrefsOnFileThread( + ChromeThread::ID calling_thread_id, + PrefStore* new_managed_pref_store, + PrefStore* new_recommended_pref_store, + AfterRefreshCallback* callback_pointer) { + scoped_ptr<AfterRefreshCallback> callback(callback_pointer); + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + scoped_ptr<PrefStore> managed_pref_store(new_managed_pref_store); + scoped_ptr<PrefStore> recommended_pref_store(new_recommended_pref_store); + + PrefStore::PrefReadError read_error = new_managed_pref_store->ReadPrefs(); + if (read_error != PrefStore::PREF_READ_ERROR_NONE) { + LOG(ERROR) << "refresh of managed policy failed: PrefReadError = " + << read_error; + return; + } + + read_error = new_recommended_pref_store->ReadPrefs(); + if (read_error != PrefStore::PREF_READ_ERROR_NONE) { + LOG(ERROR) << "refresh of recommended policy failed: PrefReadError = " + << read_error; + return; + } + + ChromeThread::PostTask( + calling_thread_id, FROM_HERE, + NewRunnableMethod(this, + &PrefValueStore::RefreshPolicyPrefsCompletion, + managed_pref_store.release(), + recommended_pref_store.release(), + callback.release())); +} + +void PrefValueStore::RefreshPolicyPrefs( + PrefStore* new_managed_pref_store, + PrefStore* new_recommended_pref_store, + AfterRefreshCallback* callback) { + ChromeThread::ID current_thread_id; + CHECK(ChromeThread::GetCurrentThreadIdentifier(¤t_thread_id)); + ChromeThread::PostTask( + ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &PrefValueStore::RefreshPolicyPrefsOnFileThread, + current_thread_id, + new_managed_pref_store, + new_recommended_pref_store, + callback)); +} + +PrefValueStore::PrefValueStore(PrefStore* managed_prefs, + PrefStore* extension_prefs, + PrefStore* command_line_prefs, + PrefStore* user_prefs, + PrefStore* recommended_prefs) { + pref_stores_[PrefNotifier::MANAGED_STORE].reset(managed_prefs); + pref_stores_[PrefNotifier::EXTENSION_STORE].reset(extension_prefs); + pref_stores_[PrefNotifier::COMMAND_LINE_STORE].reset(command_line_prefs); + pref_stores_[PrefNotifier::USER_STORE].reset(user_prefs); + pref_stores_[PrefNotifier::RECOMMENDED_STORE].reset(recommended_prefs); +} diff --git a/chrome/browser/prefs/pref_value_store.h b/chrome/browser/prefs/pref_value_store.h new file mode 100644 index 0000000..fb1cb21 --- /dev/null +++ b/chrome/browser/prefs/pref_value_store.h @@ -0,0 +1,198 @@ +// 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. + +#ifndef CHROME_BROWSER_PREFS_PREF_VALUE_STORE_H_ +#define CHROME_BROWSER_PREFS_PREF_VALUE_STORE_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/prefs/pref_notifier.h" +#include "chrome/common/pref_store.h" + +class FilePath; +class PrefStore; +class Profile; +class Value; + +// The PrefValueStore manages various sources of values for Preferences +// (e.g., configuration policies, extensions, and user settings). It returns +// the value of a Preference from the source with the highest priority, and +// allows setting user-defined values for preferences that are not managed. +// See PrefNotifier for a list of the available preference sources (PrefStores) +// and their descriptions. +// +// Unless otherwise explicitly noted, all of the methods of this class must +// be called on the UI thread. +class PrefValueStore : public base::RefCountedThreadSafe<PrefValueStore> { + public: + // Returns a new PrefValueStore with all applicable PrefStores. The + // |pref_filename| points to the user preference file. The |profile| is the + // one to which these preferences apply; it may be NULL if we're dealing + // with the local state. If |pref_filename| is empty, the user PrefStore will + // not be created. If |user_only| is true, no PrefStores will be created + // other than the user PrefStore (if |pref_filename| is not empty). This + // should not normally be called directly: the usual way to create a + // PrefValueStore is by creating a PrefService. + static PrefValueStore* CreatePrefValueStore(const FilePath& pref_filename, + Profile* profile, + bool user_only); + + ~PrefValueStore(); + + // Get the preference value for the given preference name. + // Return true if a value for the given preference name was found. + bool GetValue(const std::string& name, Value** out_value) const; + + // Read preference values into the three PrefStores so that they are available + // through the GetValue method. Return the first error that occurs (but + // continue reading the remaining PrefStores). + PrefStore::PrefReadError ReadPrefs(); + + // Persists prefs (to disk or elsewhere). Returns true if writing values was + // successful. In practice, only the user prefs are expected to be written + // out. + bool WritePrefs(); + + // Calls the method ScheduleWritePrefs on the PrefStores. In practice, only + // the user prefs are expected to be written out. + void ScheduleWritePrefs(); + + // Returns true if the PrefValueStore contains the given preference. + bool HasPrefPath(const char* name) const; + + // Returns true if the effective value of the preference has changed from its + // |old_value| (which should be the effective value of the preference as + // reported by GetValue() or the PrefService before the PrefStore changed it), + // or if the store controlling the pref has changed. Virtual so it can be + // mocked for a unit test. + // TODO(pamg): If we're setting the same value as we already had, into the + // same store that was controlling it before, and there's also a value set in + // a lower-priority store, *and* we're not the highest-priority store, then + // this will return true when it shouldn't. Fix that if it causes problems. + virtual bool PrefHasChanged(const char* path, + PrefNotifier::PrefStoreType new_store, + const Value* old_value); + + // Returns true if the PrefValueStore is read-only. + // Because the managed and recommended PrefStores are always read-only, the + // PrefValueStore as a whole is read-only if the PrefStore containing the user + // preferences is read-only. + bool ReadOnly(); + + // Alters the user-defined value of a preference. Even if the preference is + // managed this method allows the user-defined value of the preference to be + // set. But GetValue calls will not return this value as long as the + // preference is managed. Instead GetValue will return the managed value + // of the preference. Note that the PrefValueStore takes the ownership of + // the value referenced by |in_value|. It is an error to call this when no + // user PrefStore has been set. + void SetUserPrefValue(const char* name, Value* in_value); + + // Removes a value from the PrefValueStore. If a preference is managed + // or recommended this function should have no effect. + void RemoveUserPrefValue(const char* name); + + // These methods return true if a preference with the given name is in the + // indicated pref store, even if that value is currently being overridden by + // a higher-priority source. + bool PrefValueInManagedStore(const char* name); + bool PrefValueInExtensionStore(const char* name); + bool PrefValueInUserStore(const char* name); + + // These methods return true if a preference with the given name is actually + // being controlled by the indicated pref store and not being overridden by + // a higher-priority source. + bool PrefValueFromExtensionStore(const char* name); + bool PrefValueFromUserStore(const char* name); + + // Check whether a Preference value is modifiable by the user, i.e. whether + // there is no higher-priority source controlling it. + bool PrefValueUserModifiable(const char* name); + + // Signature of callback triggered after policy refresh. Parameter is not + // passed as reference to prevent passing along a pointer to a set whose + // lifecycle is managed in another thread. + typedef Callback1<std::vector<std::string> >::Type AfterRefreshCallback; + + // Called as a result of a notification of policy change. Triggers a + // reload of managed preferences from policy. Caller must pass in + // new, uninitialized managed and recommended PrefStores in + // |managed_pref_store| and |recommended_pref_store| respectively, since + // PrefValueStore doesn't know about policy-specific PrefStores. + // |callback| is called with the set of preferences changed by the policy + // refresh. |callback| is called on the caller's thread as a Task + // after RefreshPolicyPrefs has returned. RefreshPolicyPrefs takes ownership + // of the |callback| object. + void RefreshPolicyPrefs(PrefStore* managed_pref_store, + PrefStore* recommended_pref_store, + AfterRefreshCallback* callback); + + protected: + // In decreasing order of precedence: + // |managed_prefs| contains all managed (policy) preference values. + // |extension_prefs| contains preference values set by extensions. + // |command_line_prefs| contains preference values set by command-line + // switches. + // |user_prefs| contains all user-set preference values. + // |recommended_prefs| contains all recommended (policy) preference values. + // + // This constructor should only be used internally, or by subclasses in + // testing. The usual way to create a PrefValueStore is by creating a + // PrefService. + PrefValueStore(PrefStore* managed_prefs, + PrefStore* extension_prefs, + PrefStore* command_line_prefs, + PrefStore* user_prefs, + PrefStore* recommended_prefs); + + private: + friend class PrefValueStoreTest; + FRIEND_TEST_ALL_PREFIXES(PrefValueStoreTest, + TestRefreshPolicyPrefsCompletion); + + scoped_ptr<PrefStore> pref_stores_[PrefNotifier::PREF_STORE_TYPE_MAX + 1]; + + bool PrefValueInStore(const char* name, PrefNotifier::PrefStoreType type); + + // Returns true if the preference |name| is found in any PrefStore starting + // just beyond the |boundary|, non-inclusive, and checking either + // higher-priority stores (if |higher_priority| is true) or lower-priority + // stores. + bool PrefValueInStoreRange(const char* name, + PrefNotifier::PrefStoreType boundary, + bool higher_priority); + + // Returns the pref store type identifying the source that controls the + // Preference identified by |name|. If none of the sources has a value, + // INVALID is returned. + PrefNotifier::PrefStoreType ControllingPrefStoreForPref(const char* name); + + // Called during policy refresh after ReadPrefs completes on the thread + // that initiated the policy refresh. RefreshPolicyPrefsCompletion takes + // ownership of the |callback| object. + void RefreshPolicyPrefsCompletion( + PrefStore* new_managed_pref_store, + PrefStore* new_recommended_pref_store, + AfterRefreshCallback* callback); + + // Called during policy refresh to do the ReadPrefs on the FILE thread. + // RefreshPolicyPrefsOnFileThread takes ownership of the |callback| object. + void RefreshPolicyPrefsOnFileThread( + ChromeThread::ID calling_thread_id, + PrefStore* new_managed_pref_store, + PrefStore* new_recommended_pref_store, + AfterRefreshCallback* callback); + + DISALLOW_COPY_AND_ASSIGN(PrefValueStore); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_VALUE_STORE_H_ diff --git a/chrome/browser/prefs/pref_value_store_unittest.cc b/chrome/browser/prefs/pref_value_store_unittest.cc new file mode 100644 index 0000000..96af616 --- /dev/null +++ b/chrome/browser/prefs/pref_value_store_unittest.cc @@ -0,0 +1,663 @@ +// 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 "base/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/policy/configuration_policy_pref_store.h" +#include "chrome/browser/prefs/dummy_pref_store.h" +#include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/test/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Mock; + +namespace { + +class MockPolicyRefreshCallback { + public: + MockPolicyRefreshCallback() {} + MOCK_METHOD1(DoCallback, void(const std::vector<std::string>)); +}; + +} // namespace + +// Names of the preferences used in this test program. +namespace prefs { + const char kCurrentThemeID[] = "extensions.theme.id"; + const char kDeleteCache[] = "browser.clear_data.cache"; + const char kHomepage[] = "homepage"; + const char kMaxTabs[] = "tabs.max_tabs"; + const char kMissingPref[] = "this.pref.does_not_exist"; + const char kRecommendedPref[] = "this.pref.recommended_value_only"; + const char kSampleDict[] = "sample.dict"; + const char kSampleList[] = "sample.list"; + + // This must match the actual pref name so the command-line store knows about + // it. + const char kApplicationLocale[] = "intl.app_locale"; +} + +// Potentially expected values of all preferences used in this test program. +// The "user" namespace is defined globally in an ARM system header, so we need +// something different here. +namespace user_pref { + const int kMaxTabsValue = 31; + const bool kDeleteCacheValue = true; + const char kCurrentThemeIDValue[] = "abcdefg"; + const char kHomepageValue[] = "http://www.google.com"; + const char kApplicationLocaleValue[] = "is-WRONG"; +} + +namespace enforced_pref { + const std::string kHomepageValue = "http://www.topeka.com"; +} + +namespace extension_pref { + const char kCurrentThemeIDValue[] = "set by extension"; + const char kHomepageValue[] = "http://www.chromium.org"; +} + +namespace command_line_pref { + const char kApplicationLocaleValue[] = "hi-MOM"; + const char kCurrentThemeIDValue[] = "zyxwvut"; + const char kHomepageValue[] = "http://www.ferretcentral.org"; +} + +namespace recommended_pref { + const int kMaxTabsValue = 10; + const bool kRecommendedPrefValue = true; +} + +class PrefValueStoreTest : public testing::Test { + protected: + virtual void SetUp() { + // Create dummy user preferences. + enforced_prefs_= CreateEnforcedPrefs(); + extension_prefs_ = CreateExtensionPrefs(); + command_line_prefs_ = CreateCommandLinePrefs(); + user_prefs_ = CreateUserPrefs(); + recommended_prefs_ = CreateRecommendedPrefs(); + + // Create |DummyPrefStore|s. + enforced_pref_store_ = new DummyPrefStore(); + enforced_pref_store_->set_prefs(enforced_prefs_); + extension_pref_store_ = new DummyPrefStore(); + extension_pref_store_->set_prefs(extension_prefs_); + command_line_pref_store_ = new DummyPrefStore(); + command_line_pref_store_->set_prefs(command_line_prefs_); + user_pref_store_ = new DummyPrefStore(); + user_pref_store_->set_read_only(false); + user_pref_store_->set_prefs(user_prefs_); + recommended_pref_store_ = new DummyPrefStore(); + recommended_pref_store_->set_prefs(recommended_prefs_); + + // Create a new pref-value-store. + pref_value_store_ = new TestingPrefService::TestingPrefValueStore( + enforced_pref_store_, + extension_pref_store_, + command_line_pref_store_, + user_pref_store_, + recommended_pref_store_); + + ui_thread_.reset(new ChromeThread(ChromeThread::UI, &loop_)); + file_thread_.reset(new ChromeThread(ChromeThread::FILE, &loop_)); + } + + // Creates a new dictionary and stores some sample user preferences + // in it. + DictionaryValue* CreateUserPrefs() { + DictionaryValue* user_prefs = new DictionaryValue(); + user_prefs->SetBoolean(prefs::kDeleteCache, user_pref::kDeleteCacheValue); + user_prefs->SetInteger(prefs::kMaxTabs, user_pref::kMaxTabsValue); + user_prefs->SetString(prefs::kCurrentThemeID, + user_pref::kCurrentThemeIDValue); + user_prefs->SetString(prefs::kApplicationLocale, + user_pref::kApplicationLocaleValue); + user_prefs->SetString(prefs::kHomepage, user_pref::kHomepageValue); + return user_prefs; + } + + DictionaryValue* CreateEnforcedPrefs() { + DictionaryValue* enforced_prefs = new DictionaryValue(); + enforced_prefs->SetString(prefs::kHomepage, enforced_pref::kHomepageValue); + expected_differing_paths_.push_back(prefs::kHomepage); + return enforced_prefs; + } + + DictionaryValue* CreateExtensionPrefs() { + DictionaryValue* extension_prefs = new DictionaryValue(); + extension_prefs->SetString(prefs::kCurrentThemeID, + extension_pref::kCurrentThemeIDValue); + extension_prefs->SetString(prefs::kHomepage, + extension_pref::kHomepageValue); + return extension_prefs; + } + + DictionaryValue* CreateCommandLinePrefs() { + DictionaryValue* command_line_prefs = new DictionaryValue(); + command_line_prefs->SetString(prefs::kCurrentThemeID, + command_line_pref::kCurrentThemeIDValue); + command_line_prefs->SetString(prefs::kApplicationLocale, + command_line_pref::kApplicationLocaleValue); + command_line_prefs->SetString(prefs::kHomepage, + command_line_pref::kHomepageValue); + return command_line_prefs; + } + + DictionaryValue* CreateRecommendedPrefs() { + DictionaryValue* recommended_prefs = new DictionaryValue(); + recommended_prefs->SetInteger(prefs::kMaxTabs, + recommended_pref::kMaxTabsValue); + recommended_prefs->SetBoolean( + prefs::kRecommendedPref, + recommended_pref::kRecommendedPrefValue); + + // Expected differing paths must be added in lexicographic order + // to work properly + expected_differing_paths_.push_back("tabs"); + expected_differing_paths_.push_back(prefs::kMaxTabs); + expected_differing_paths_.push_back("this"); + expected_differing_paths_.push_back("this.pref"); + expected_differing_paths_.push_back(prefs::kRecommendedPref); + return recommended_prefs; } + + DictionaryValue* CreateSampleDictValue() { + DictionaryValue* sample_dict = new DictionaryValue(); + sample_dict->SetBoolean("issample", true); + sample_dict->SetInteger("value", 4); + sample_dict->SetString("descr", "Sample Test Dictionary"); + return sample_dict; + } + + ListValue* CreateSampleListValue() { + ListValue* sample_list = new ListValue(); + sample_list->Set(0, Value::CreateIntegerValue(0)); + sample_list->Set(1, Value::CreateIntegerValue(1)); + sample_list->Set(2, Value::CreateIntegerValue(2)); + sample_list->Set(3, Value::CreateIntegerValue(3)); + return sample_list; + } + + virtual void TearDown() { + loop_.RunAllPending(); + } + + MessageLoop loop_; + + scoped_refptr<TestingPrefService::TestingPrefValueStore> pref_value_store_; + + // |PrefStore|s are owned by the |PrefValueStore|. + DummyPrefStore* enforced_pref_store_; + DummyPrefStore* extension_pref_store_; + DummyPrefStore* command_line_pref_store_; + DummyPrefStore* recommended_pref_store_; + DummyPrefStore* user_pref_store_; + + // A vector of the preferences paths in the managed and recommended + // PrefStores that are set at the beginning of a test. Can be modified + // by the test to track changes that it makes to the preferences + // stored in the managed and recommended PrefStores. + std::vector<std::string> expected_differing_paths_; + + // Preferences are owned by the individual |DummyPrefStores|. + DictionaryValue* enforced_prefs_; + DictionaryValue* extension_prefs_; + DictionaryValue* command_line_prefs_; + DictionaryValue* user_prefs_; + DictionaryValue* recommended_prefs_; + + private: + scoped_ptr<ChromeThread> ui_thread_; + scoped_ptr<ChromeThread> file_thread_; +}; + +TEST_F(PrefValueStoreTest, IsReadOnly) { + enforced_pref_store_->set_read_only(true); + extension_pref_store_->set_read_only(true); + command_line_pref_store_->set_read_only(true); + user_pref_store_->set_read_only(true); + recommended_pref_store_->set_read_only(true); + EXPECT_TRUE(pref_value_store_->ReadOnly()); + + user_pref_store_->set_read_only(false); + EXPECT_FALSE(pref_value_store_->ReadOnly()); +} + +TEST_F(PrefValueStoreTest, GetValue) { + Value* value; + + // Test getting an enforced value overwriting a user-defined and + // extension-defined value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kHomepage, &value)); + std::string actual_str_value; + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(enforced_pref::kHomepageValue, actual_str_value); + + // Test getting an extension value overwriting a user-defined and + // command-line-defined value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kCurrentThemeID, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(extension_pref::kCurrentThemeIDValue, actual_str_value); + + // Test getting a command-line value overwriting a user-defined value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kApplicationLocale, &value)); + EXPECT_TRUE(value->GetAsString(&actual_str_value)); + EXPECT_EQ(command_line_pref::kApplicationLocaleValue, actual_str_value); + + // Test getting a user-set value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kDeleteCache, &value)); + bool actual_bool_value = false; + EXPECT_TRUE(value->GetAsBoolean(&actual_bool_value)); + EXPECT_EQ(user_pref::kDeleteCacheValue, actual_bool_value); + + // Test getting a user set value overwriting a recommended value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kMaxTabs, &value)); + int actual_int_value = -1; + EXPECT_TRUE(value->GetAsInteger(&actual_int_value)); + EXPECT_EQ(user_pref::kMaxTabsValue, actual_int_value); + + // Test getting a recommended value. + value = NULL; + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kRecommendedPref, &value)); + actual_bool_value = false; + EXPECT_TRUE(value->GetAsBoolean(&actual_bool_value)); + EXPECT_EQ(recommended_pref::kRecommendedPrefValue, actual_bool_value); + + // Test getting a preference value that the |PrefValueStore| + // does not contain. + FundamentalValue tmp_dummy_value(true); + Value* v_null = &tmp_dummy_value; + ASSERT_FALSE(pref_value_store_->GetValue(prefs::kMissingPref, &v_null)); + ASSERT_TRUE(v_null == NULL); +} + +TEST_F(PrefValueStoreTest, HasPrefPath) { + // Enforced preference + EXPECT_TRUE(pref_value_store_->HasPrefPath(prefs::kHomepage)); + // User preference + EXPECT_TRUE(pref_value_store_->HasPrefPath(prefs::kDeleteCache)); + // Recommended preference + EXPECT_TRUE(pref_value_store_->HasPrefPath(prefs::kRecommendedPref)); + // Unknown preference + EXPECT_FALSE(pref_value_store_->HasPrefPath(prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefHasChanged) { + // Pref controlled by highest-priority store, set to same value in same store. + const char managed_pref_path[] = "managed_pref"; + const char same_str[] = "same value"; + scoped_ptr<Value> same_value(Value::CreateStringValue(same_str)); + enforced_pref_store_->prefs()->SetString(managed_pref_path, same_str); + EXPECT_FALSE(pref_value_store_->PrefHasChanged(managed_pref_path, + static_cast<PrefNotifier::PrefStoreType>(0), same_value.get())); + + // Pref controlled by highest-priority store, set to different value in + // same store. + const char other_str[] = "other value"; + scoped_ptr<Value> other_value(Value::CreateStringValue(other_str)); + EXPECT_TRUE(pref_value_store_->PrefHasChanged(managed_pref_path, + static_cast<PrefNotifier::PrefStoreType>(0), other_value.get())); + + // Pref controlled by user store, set to same value in user store, no lower + // store has a value. + const char user_pref_path[] = "user_pref"; + user_pref_store_->prefs()->SetString(user_pref_path, same_str); + EXPECT_FALSE(pref_value_store_->PrefHasChanged(user_pref_path, + PrefNotifier::USER_STORE, same_value.get())); + + // Pref controlled by user store, set to new value in user store, no lower + // store has a value. + EXPECT_TRUE(pref_value_store_->PrefHasChanged(user_pref_path, + PrefNotifier::USER_STORE, other_value.get())); + + // Pref controlled by user store, set to same value in user store, some lower + // store has a value. + const char third_str[] = "third value"; + recommended_pref_store_->prefs()->SetString(user_pref_path, third_str); + // This is not necessarily the correct behavior, but it is the current + // behavior. See comments in pref_value_store.h and .cc. + EXPECT_TRUE(pref_value_store_->PrefHasChanged(user_pref_path, + PrefNotifier::USER_STORE, same_value.get())); + + // Pref controlled by user store, set to new value in user store, some lower + // store has a value. + EXPECT_TRUE(pref_value_store_->PrefHasChanged(user_pref_path, + PrefNotifier::USER_STORE, other_value.get())); + + // Pref controlled by user store, set to same value in managed store. + EXPECT_TRUE(pref_value_store_->PrefHasChanged(user_pref_path, + PrefNotifier::MANAGED_STORE, same_value.get())); + + // Pref controlled by user store, set to new value in managed store. + EXPECT_TRUE(pref_value_store_->PrefHasChanged(user_pref_path, + PrefNotifier::MANAGED_STORE, other_value.get())); + + // Pref controlled by highest-priority store, set to any value in user store. + EXPECT_FALSE(pref_value_store_->PrefHasChanged(managed_pref_path, + PrefNotifier::USER_STORE, same_value.get())); + + // Pref controlled by lowest-priority store, set to same value in same store. + const char recommended_pref_path[] = "recommended_pref"; + recommended_pref_store_->prefs()->SetString(recommended_pref_path, same_str); + EXPECT_FALSE(pref_value_store_->PrefHasChanged(recommended_pref_path, + PrefNotifier::PREF_STORE_TYPE_MAX, same_value.get())); + + // Pref controlled by lowest-priority store, set to different value in same + // store. + EXPECT_TRUE(pref_value_store_->PrefHasChanged(recommended_pref_path, + PrefNotifier::PREF_STORE_TYPE_MAX, other_value.get())); +} + +TEST_F(PrefValueStoreTest, ReadPrefs) { + pref_value_store_->ReadPrefs(); + // The ReadPrefs method of the |DummyPrefStore| deletes the |pref_store|s + // internal dictionary and creates a new empty dictionary. Hence this + // dictionary does not contain any of the preloaded preferences. + // This shows that the ReadPrefs method of the |DummyPrefStore| was called. + EXPECT_FALSE(pref_value_store_->HasPrefPath(prefs::kDeleteCache)); +} + +TEST_F(PrefValueStoreTest, WritePrefs) { + user_pref_store_->set_prefs_written(false); + pref_value_store_->WritePrefs(); + ASSERT_TRUE(user_pref_store_->get_prefs_written()); +} + +TEST_F(PrefValueStoreTest, SetUserPrefValue) { + Value* new_value = NULL; + Value* actual_value = NULL; + + // Test that enforced values can not be set. + ASSERT_TRUE(pref_value_store_->PrefValueInManagedStore(prefs::kHomepage)); + // The Ownership is tranfered to |PrefValueStore|. + new_value = Value::CreateStringValue("http://www.youtube.com"); + pref_value_store_->SetUserPrefValue(prefs::kHomepage, new_value); + + ASSERT_TRUE(pref_value_store_->GetValue(prefs::kHomepage, &actual_value)); + std::string value_str; + actual_value->GetAsString(&value_str); + ASSERT_EQ(enforced_pref::kHomepageValue, value_str); + + // User preferences values can be set + ASSERT_FALSE(pref_value_store_->PrefValueInManagedStore(prefs::kMaxTabs)); + actual_value = NULL; + pref_value_store_->GetValue(prefs::kMaxTabs, &actual_value); + int int_value; + EXPECT_TRUE(actual_value->GetAsInteger(&int_value)); + EXPECT_EQ(user_pref::kMaxTabsValue, int_value); + + new_value = Value::CreateIntegerValue(1); + pref_value_store_->SetUserPrefValue(prefs::kMaxTabs, new_value); + actual_value = NULL; + pref_value_store_->GetValue(prefs::kMaxTabs, &actual_value); + EXPECT_TRUE(new_value->Equals(actual_value)); + + // Set and Get |DictionaryValue| + DictionaryValue* expected_dict_value = CreateSampleDictValue(); + pref_value_store_->SetUserPrefValue(prefs::kSampleDict, expected_dict_value); + + actual_value = NULL; + std::string key(prefs::kSampleDict); + pref_value_store_->GetValue(key , &actual_value); + + ASSERT_EQ(expected_dict_value, actual_value); + ASSERT_TRUE(expected_dict_value->Equals(actual_value)); + + // Set and Get a |ListValue| + ListValue* expected_list_value = CreateSampleListValue(); + pref_value_store_->SetUserPrefValue(prefs::kSampleList, expected_list_value); + + actual_value = NULL; + key = prefs::kSampleList; + pref_value_store_->GetValue(key, &actual_value); + + ASSERT_EQ(expected_list_value, actual_value); + ASSERT_TRUE(expected_list_value->Equals(actual_value)); +} + +TEST_F(PrefValueStoreTest, PrefValueInManagedStore) { + // Test an enforced preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kHomepage)); + EXPECT_TRUE(pref_value_store_->PrefValueInManagedStore(prefs::kHomepage)); + + // Test an extension preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kCurrentThemeID)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kCurrentThemeID)); + + // Test a command-line preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kApplicationLocale)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kApplicationLocale)); + + // Test a user preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kMaxTabs)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(prefs::kMaxTabs)); + + // Test a preference from the recommended pref store. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore( + prefs::kRecommendedPref)); + + // Test a preference for which the PrefValueStore does not contain a value. + ASSERT_FALSE(pref_value_store_->HasPrefPath(prefs::kMissingPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueInExtensionStore) { + // Test an enforced preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kHomepage)); + EXPECT_TRUE(pref_value_store_->PrefValueInExtensionStore(prefs::kHomepage)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kHomepage)); + + // Test an extension preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kCurrentThemeID)); + EXPECT_TRUE(pref_value_store_->PrefValueInExtensionStore( + prefs::kCurrentThemeID)); + EXPECT_TRUE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kCurrentThemeID)); + + // Test a command-line preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kApplicationLocale)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kApplicationLocale)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kApplicationLocale)); + + // Test a user preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kMaxTabs)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore(prefs::kMaxTabs)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore(prefs::kMaxTabs)); + + // Test a preference from the recommended pref store. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kRecommendedPref)); + + // Test a preference for which the PrefValueStore does not contain a value. + ASSERT_FALSE(pref_value_store_->HasPrefPath(prefs::kMissingPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore( + prefs::kMissingPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore( + prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, PrefValueInUserStore) { + // Test an enforced preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kHomepage)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(prefs::kHomepage)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(prefs::kHomepage)); + + // Test an extension preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kCurrentThemeID)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore( + prefs::kCurrentThemeID)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kCurrentThemeID)); + + // Test a command-line preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kApplicationLocale)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore( + prefs::kApplicationLocale)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kApplicationLocale)); + + // Test a user preference. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kMaxTabs)); + EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(prefs::kMaxTabs)); + EXPECT_TRUE(pref_value_store_->PrefValueFromUserStore(prefs::kMaxTabs)); + + // Test a preference from the recommended pref store. + ASSERT_TRUE(pref_value_store_->HasPrefPath(prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInUserStore( + prefs::kRecommendedPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore( + prefs::kRecommendedPref)); + + // Test a preference for which the PrefValueStore does not contain a value. + ASSERT_FALSE(pref_value_store_->HasPrefPath(prefs::kMissingPref)); + EXPECT_FALSE(pref_value_store_->PrefValueInUserStore(prefs::kMissingPref)); + EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(prefs::kMissingPref)); +} + +TEST_F(PrefValueStoreTest, TestPolicyRefresh) { + // pref_value_store_ is initialized by PrefValueStoreTest to have values + // in both it's managed and recommended store. By replacing them with + // dummy stores, all of the paths of the prefs originally managed and + // recommended stores should change. + MockPolicyRefreshCallback callback; + EXPECT_CALL(callback, DoCallback(_)).Times(0); + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + pref_value_store_.get(), + &PrefValueStore::RefreshPolicyPrefs, + new DummyPrefStore(), + new DummyPrefStore(), + NewCallback(&callback, + &MockPolicyRefreshCallback::DoCallback))); + Mock::VerifyAndClearExpectations(&callback); + EXPECT_CALL(callback, DoCallback(expected_differing_paths_)).Times(1); + loop_.RunAllPending(); +} + +TEST_F(PrefValueStoreTest, TestRefreshPolicyPrefsCompletion) { + // Test changed preferences in managed store and removed + // preferences in the recommended store. In addition + // to "homepage", the other prefs that are set by default in + // the test class are removed by the DummyStore + scoped_ptr<DummyPrefStore> new_managed_store(new DummyPrefStore()); + DictionaryValue* dict = new DictionaryValue(); + dict->SetString("homepage", "some other changed homepage"); + new_managed_store->set_prefs(dict); + MockPolicyRefreshCallback callback; + EXPECT_CALL(callback, DoCallback(expected_differing_paths_)).Times(1); + pref_value_store_->RefreshPolicyPrefsCompletion( + new_managed_store.release(), + new DummyPrefStore(), + NewCallback(&callback, + &MockPolicyRefreshCallback::DoCallback)); + + // Test properties that have been removed from the managed store. + // Homepage is still set in managed prefs. + expected_differing_paths_.clear(); + expected_differing_paths_.push_back(std::string("homepage")); + MockPolicyRefreshCallback callback2; + EXPECT_CALL(callback2, DoCallback(expected_differing_paths_)).Times(1); + pref_value_store_->RefreshPolicyPrefsCompletion( + new DummyPrefStore(), + new DummyPrefStore(), + NewCallback(&callback2, + &MockPolicyRefreshCallback::DoCallback)); + + // Test properties that are added to the recommended store. + scoped_ptr<DummyPrefStore> new_recommended_store(new DummyPrefStore()); + dict = new DictionaryValue(); + dict->SetString("homepage", "some other changed homepage 2"); + new_recommended_store->set_prefs(dict); + expected_differing_paths_.clear(); + expected_differing_paths_.push_back(std::string("homepage")); + MockPolicyRefreshCallback callback3; + EXPECT_CALL(callback3, DoCallback(expected_differing_paths_)).Times(1); + pref_value_store_->RefreshPolicyPrefsCompletion( + new DummyPrefStore(), + new_recommended_store.release(), + NewCallback(&callback3, + &MockPolicyRefreshCallback::DoCallback)); + + // Test adding a multi-key path. + new_managed_store.reset(new DummyPrefStore()); + dict = new DictionaryValue(); + dict->SetString("segment1.segment2", "value"); + new_managed_store->set_prefs(dict); + expected_differing_paths_.clear(); + expected_differing_paths_.push_back(std::string("homepage")); + expected_differing_paths_.push_back(std::string("segment1")); + expected_differing_paths_.push_back(std::string("segment1.segment2")); + MockPolicyRefreshCallback callback4; + EXPECT_CALL(callback4, DoCallback(expected_differing_paths_)).Times(1); + pref_value_store_->RefreshPolicyPrefsCompletion( + new_managed_store.release(), + new DummyPrefStore(), + NewCallback(&callback4, + &MockPolicyRefreshCallback::DoCallback)); +} + +TEST_F(PrefValueStoreTest, TestConcurrentPolicyRefresh) { + MockPolicyRefreshCallback callback1; + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + pref_value_store_.get(), + &PrefValueStore::RefreshPolicyPrefs, + new DummyPrefStore(), + new DummyPrefStore(), + NewCallback(&callback1, + &MockPolicyRefreshCallback::DoCallback))); + EXPECT_CALL(callback1, DoCallback(_)).Times(0); + + MockPolicyRefreshCallback callback2; + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + pref_value_store_.get(), + &PrefValueStore::RefreshPolicyPrefs, + new DummyPrefStore(), + new DummyPrefStore(), + NewCallback(&callback2, + &MockPolicyRefreshCallback::DoCallback))); + EXPECT_CALL(callback2, DoCallback(_)).Times(0); + + MockPolicyRefreshCallback callback3; + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + pref_value_store_.get(), + &PrefValueStore::RefreshPolicyPrefs, + new DummyPrefStore(), + new DummyPrefStore(), + NewCallback(&callback3, + &MockPolicyRefreshCallback::DoCallback))); + EXPECT_CALL(callback3, DoCallback(_)).Times(0); + Mock::VerifyAndClearExpectations(&callback1); + Mock::VerifyAndClearExpectations(&callback2); + Mock::VerifyAndClearExpectations(&callback3); + + EXPECT_CALL(callback1, DoCallback(expected_differing_paths_)).Times(1); + std::vector<std::string> no_differing_paths; + EXPECT_CALL(callback2, DoCallback(no_differing_paths)).Times(1); + EXPECT_CALL(callback3, DoCallback(no_differing_paths)).Times(1); + loop_.RunAllPending(); +} diff --git a/chrome/browser/prefs/scoped_pref_update.cc b/chrome/browser/prefs/scoped_pref_update.cc new file mode 100644 index 0000000..de48b4d --- /dev/null +++ b/chrome/browser/prefs/scoped_pref_update.cc @@ -0,0 +1,20 @@ +// 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 "base/utf_string_conversions.h" // TODO(viettrungluu): remove +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/scoped_pref_update.h" + +ScopedPrefUpdate::ScopedPrefUpdate(PrefService* service, const char* path) + : service_(service), + path_(path) {} + +// TODO(viettrungluu): deprecate and remove #include +ScopedPrefUpdate::ScopedPrefUpdate(PrefService* service, const wchar_t* path) + : service_(service), + path_(WideToUTF8(path)) {} + +ScopedPrefUpdate::~ScopedPrefUpdate() { + service_->pref_notifier()->FireObservers(path_.c_str()); +} diff --git a/chrome/browser/prefs/scoped_pref_update.h b/chrome/browser/prefs/scoped_pref_update.h new file mode 100644 index 0000000..af84866 --- /dev/null +++ b/chrome/browser/prefs/scoped_pref_update.h @@ -0,0 +1,26 @@ +// 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. +// +// A helper class that assists preferences in firing notifications when lists +// are changed. + +#ifndef CHROME_BROWSER_PREFS_SCOPED_PREF_UPDATE_H_ +#define CHROME_BROWSER_PREFS_SCOPED_PREF_UPDATE_H_ +#pragma once + +#include "chrome/browser/prefs/pref_service.h" + +class ScopedPrefUpdate { + public: + ScopedPrefUpdate(PrefService* service, const char* path); + // TODO(viettrungluu): deprecate: + ScopedPrefUpdate(PrefService* service, const wchar_t* path); + ~ScopedPrefUpdate(); + + private: + PrefService* service_; + std::string path_; +}; + +#endif // CHROME_BROWSER_PREFS_SCOPED_PREF_UPDATE_H_ diff --git a/chrome/browser/prefs/session_startup_pref.cc b/chrome/browser/prefs/session_startup_pref.cc new file mode 100644 index 0000000..827af01 --- /dev/null +++ b/chrome/browser/prefs/session_startup_pref.cc @@ -0,0 +1,107 @@ +// 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/prefs/session_startup_pref.h" + +#include <string> + +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/scoped_pref_update.h" +#include "chrome/browser/profile.h" +#include "chrome/common/pref_names.h" + +namespace { + +// For historical reasons the enum and value registered in the prefs don't line +// up. These are the values registered in prefs. +const int kPrefValueDefault = 0; +const int kPrefValueLast = 1; +const int kPrefValueURLs = 4; + +// Converts a SessionStartupPref::Type to an integer written to prefs. +int TypeToPrefValue(SessionStartupPref::Type type) { + switch (type) { + case SessionStartupPref::LAST: return kPrefValueLast; + case SessionStartupPref::URLS: return kPrefValueURLs; + default: return kPrefValueDefault; + } +} + +// Converts an integer pref value to a SessionStartupPref::Type. +SessionStartupPref::Type PrefValueToType(int pref_value) { + switch (pref_value) { + case kPrefValueLast: return SessionStartupPref::LAST; + case kPrefValueURLs: return SessionStartupPref::URLS; + default: return SessionStartupPref::DEFAULT; + } +} + +} // namespace + +// static +void SessionStartupPref::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterIntegerPref(prefs::kRestoreOnStartup, + TypeToPrefValue(browser_defaults::kDefaultSessionStartupType)); + prefs->RegisterListPref(prefs::kURLsToRestoreOnStartup); +} + +// static +void SessionStartupPref::SetStartupPref( + Profile* profile, + const SessionStartupPref& pref) { + DCHECK(profile); + SetStartupPref(profile->GetPrefs(), pref); +} + +// static +void SessionStartupPref::SetStartupPref(PrefService* prefs, + const SessionStartupPref& pref) { + DCHECK(prefs); + prefs->SetInteger(prefs::kRestoreOnStartup, TypeToPrefValue(pref.type)); + + // Always save the URLs, that way the UI can remain consistent even if the + // user changes the startup type pref. + // Ownership of the ListValue retains with the pref service. + ScopedPrefUpdate update(prefs, prefs::kURLsToRestoreOnStartup); + ListValue* url_pref_list = + prefs->GetMutableList(prefs::kURLsToRestoreOnStartup); + DCHECK(url_pref_list); + url_pref_list->Clear(); + for (size_t i = 0; i < pref.urls.size(); ++i) { + url_pref_list->Set(static_cast<int>(i), + new StringValue(pref.urls[i].spec())); + } +} + +// static +SessionStartupPref SessionStartupPref::GetStartupPref(Profile* profile) { + DCHECK(profile); + return GetStartupPref(profile->GetPrefs()); +} + +// static +SessionStartupPref SessionStartupPref::GetStartupPref(PrefService* prefs) { + DCHECK(prefs); + SessionStartupPref pref( + PrefValueToType(prefs->GetInteger(prefs::kRestoreOnStartup))); + + // Always load the urls, even if the pref type isn't URLS. This way the + // preferenes panels can show the user their last choice. + ListValue* url_pref_list = prefs->GetMutableList( + prefs::kURLsToRestoreOnStartup); + DCHECK(url_pref_list); + for (size_t i = 0; i < url_pref_list->GetSize(); ++i) { + Value* value = NULL; + if (url_pref_list->Get(i, &value)) { + std::string url_text; + if (value->GetAsString(&url_text)) + pref.urls.push_back(GURL(url_text)); + } + } + + return pref; +} diff --git a/chrome/browser/prefs/session_startup_pref.h b/chrome/browser/prefs/session_startup_pref.h new file mode 100644 index 0000000..a2a1660 --- /dev/null +++ b/chrome/browser/prefs/session_startup_pref.h @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_PREFS_SESSION_STARTUP_PREF_H__ +#define CHROME_BROWSER_PREFS_SESSION_STARTUP_PREF_H__ +#pragma once + +#include <vector> + +#include "googleurl/src/gurl.h" + +class PrefService; +class Profile; + +// StartupPref specifies what should happen at startup for a specified profile. +// StartupPref is stored in the preferences for a particular profile. +struct SessionStartupPref { + enum Type { + // Indicates the user doesn't want to restore a previous session. + DEFAULT, + + // Indicates the user wants to restore the last session. + LAST, + + // Indicates the user wants to restore a specific set of URLs. The URLs + // are contained in urls. + URLS + }; + + static void RegisterUserPrefs(PrefService* prefs); + + // What should happen on startup for the specified profile. + static void SetStartupPref(Profile* profile, const SessionStartupPref& pref); + static void SetStartupPref(PrefService* prefs, + const SessionStartupPref& pref); + static SessionStartupPref GetStartupPref(Profile* profile); + static SessionStartupPref GetStartupPref(PrefService* prefs); + + SessionStartupPref() : type(DEFAULT) {} + + explicit SessionStartupPref(Type type) : type(type) {} + + // What to do on startup. + Type type; + + // The URLs to restore. Only used if type == URLS. + std::vector<GURL> urls; +}; + +#endif // CHROME_BROWSER_PREFS_SESSION_STARTUP_PREF_H__ |