diff options
author | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-12 03:41:37 +0000 |
---|---|---|
committer | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-12 03:41:37 +0000 |
commit | ea3e497b1949405ae04caa8b1dc8400672b1dfa1 (patch) | |
tree | 597971f904ab59e7e47fbe108c241e66640c5d4e | |
parent | 92a794994111f442e9c7ba1792a5418a77c2ca74 (diff) | |
download | chromium_src-ea3e497b1949405ae04caa8b1dc8400672b1dfa1.zip chromium_src-ea3e497b1949405ae04caa8b1dc8400672b1dfa1.tar.gz chromium_src-ea3e497b1949405ae04caa8b1dc8400672b1dfa1.tar.bz2 |
Keep emtpy List/Dictionary pref value with non-empty default.
- Add a MarkNeedsEmptyValue method to JsonPrefStore;
- In PrefService::RegisterPreference, mark ListValue and DictionaryValue pref
with non-empty default as needing empty value in |user_pref_store_|;
- Update ChromeLauncherDelegate to put back default pinned apps and add
migration logic for M19 users;
- Add unit tests;
BUG=122679
TEST=Verify fix for issue 122679.
Review URL: http://codereview.chromium.org/10055003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@131919 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/prefs/overlay_user_pref_store.cc | 5 | ||||
-rw-r--r-- | chrome/browser/prefs/overlay_user_pref_store.h | 1 | ||||
-rw-r--r-- | chrome/browser/prefs/pref_service.cc | 17 | ||||
-rw-r--r-- | chrome/browser/prefs/pref_service_mock_builder.cc | 14 | ||||
-rw-r--r-- | chrome/browser/prefs/pref_service_mock_builder.h | 7 | ||||
-rw-r--r-- | chrome/browser/prefs/pref_service_unittest.cc | 81 | ||||
-rw-r--r-- | chrome/browser/prefs/testing_pref_store.cc | 3 | ||||
-rw-r--r-- | chrome/browser/prefs/testing_pref_store.h | 1 | ||||
-rw-r--r-- | chrome/browser/ui/views/ash/launcher/chrome_launcher_delegate.cc | 39 | ||||
-rw-r--r-- | chrome/common/json_pref_store.cc | 28 | ||||
-rw-r--r-- | chrome/common/json_pref_store.h | 4 | ||||
-rw-r--r-- | chrome/common/json_pref_store_unittest.cc | 49 | ||||
-rw-r--r-- | chrome/common/persistent_pref_store.h | 4 | ||||
-rw-r--r-- | chrome/test/data/pref_service/read.need_empty_value.json | 10 | ||||
-rw-r--r-- | chrome/test/data/pref_service/write.golden.need_empty_value.json | 6 |
15 files changed, 251 insertions, 18 deletions
diff --git a/chrome/browser/prefs/overlay_user_pref_store.cc b/chrome/browser/prefs/overlay_user_pref_store.cc index ca4f585..8d04ec3 100644 --- a/chrome/browser/prefs/overlay_user_pref_store.cc +++ b/chrome/browser/prefs/overlay_user_pref_store.cc @@ -101,6 +101,11 @@ void OverlayUserPrefStore::RemoveValue(const std::string& key) { ReportValueChanged(key); } +void OverlayUserPrefStore::MarkNeedsEmptyValue(const std::string& key) { + if (!ShallBeStoredInOverlay(key)) + underlay_->MarkNeedsEmptyValue(key); +} + bool OverlayUserPrefStore::ReadOnly() const { return false; } diff --git a/chrome/browser/prefs/overlay_user_pref_store.h b/chrome/browser/prefs/overlay_user_pref_store.h index f0a1f4b..105dc67 100644 --- a/chrome/browser/prefs/overlay_user_pref_store.h +++ b/chrome/browser/prefs/overlay_user_pref_store.h @@ -45,6 +45,7 @@ class OverlayUserPrefStore : public PersistentPrefStore, virtual void SetValueSilently(const std::string& key, base::Value* value) OVERRIDE; virtual void RemoveValue(const std::string& key) OVERRIDE; + virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; virtual bool ReadOnly() const OVERRIDE; virtual PrefReadError GetReadError() const OVERRIDE; virtual PrefReadError ReadPrefs() OVERRIDE; diff --git a/chrome/browser/prefs/pref_service.cc b/chrome/browser/prefs/pref_service.cc index cf9f22e..de7f406 100644 --- a/chrome/browser/prefs/pref_service.cc +++ b/chrome/browser/prefs/pref_service.cc @@ -727,6 +727,23 @@ void PrefService::RegisterPreference(const char* path, DCHECK(orig_type != Value::TYPE_NULL && orig_type != Value::TYPE_BINARY) << "invalid preference type: " << orig_type; + // For ListValue and DictionaryValue with non empty default, empty value + // for |path| needs to be persisted in |user_pref_store_|. So that + // non empty default is not used when user sets an empty ListValue or + // DictionaryValue. + bool needs_empty_value = false; + if (orig_type == base::Value::TYPE_LIST) { + const base::ListValue* list = NULL; + if (default_value->GetAsList(&list) && !list->empty()) + needs_empty_value = true; + } else if (orig_type == base::Value::TYPE_DICTIONARY) { + const base::DictionaryValue* dict = NULL; + if (default_value->GetAsDictionary(&dict) && !dict->empty()) + needs_empty_value = true; + } + if (needs_empty_value) + user_pref_store_->MarkNeedsEmptyValue(path); + // Hand off ownership. default_store_->SetDefaultValue(path, scoped_value.release()); diff --git a/chrome/browser/prefs/pref_service_mock_builder.cc b/chrome/browser/prefs/pref_service_mock_builder.cc index 5b4ba24..edfe696 100644 --- a/chrome/browser/prefs/pref_service_mock_builder.cc +++ b/chrome/browser/prefs/pref_service_mock_builder.cc @@ -77,10 +77,16 @@ PrefServiceMockBuilder::WithCommandLine(CommandLine* command_line) { PrefServiceMockBuilder& PrefServiceMockBuilder::WithUserFilePrefs(const FilePath& prefs_file) { - user_prefs_ = - new JsonPrefStore(prefs_file, - BrowserThread::GetMessageLoopProxyForThread( - BrowserThread::FILE)); + return WithUserFilePrefs(prefs_file, + BrowserThread::GetMessageLoopProxyForThread( + BrowserThread::FILE)); +} + +PrefServiceMockBuilder& +PrefServiceMockBuilder::WithUserFilePrefs( + const FilePath& prefs_file, + base::MessageLoopProxy* message_loop_proxy) { + user_prefs_ = new JsonPrefStore(prefs_file, message_loop_proxy); return *this; } diff --git a/chrome/browser/prefs/pref_service_mock_builder.h b/chrome/browser/prefs/pref_service_mock_builder.h index 5ea5ace..2dd764f 100644 --- a/chrome/browser/prefs/pref_service_mock_builder.h +++ b/chrome/browser/prefs/pref_service_mock_builder.h @@ -15,6 +15,10 @@ class CommandLine; class FilePath; class PrefService; +namespace base { +class MessageLoopProxy; +} + namespace policy { class PolicyService; } @@ -46,6 +50,9 @@ class PrefServiceMockBuilder { // Specifies to use an actual file-backed user pref store. PrefServiceMockBuilder& WithUserFilePrefs(const FilePath& prefs_file); + PrefServiceMockBuilder& WithUserFilePrefs( + const FilePath& prefs_file, + base::MessageLoopProxy* message_loop_proxy); // Creates the PrefService, invalidating the entire builder configuration. PrefService* Create(); diff --git a/chrome/browser/prefs/pref_service_unittest.cc b/chrome/browser/prefs/pref_service_unittest.cc index 8a7069c..38d6ea96 100644 --- a/chrome/browser/prefs/pref_service_unittest.cc +++ b/chrome/browser/prefs/pref_service_unittest.cc @@ -5,7 +5,10 @@ #include <string> #include "base/command_line.h" +#include "base/file_util.h" #include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/scoped_temp_dir.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/policy/configuration_policy_pref_store.h" @@ -16,9 +19,11 @@ #include "chrome/browser/prefs/pref_observer_mock.h" #include "chrome/browser/prefs/pref_service_mock_builder.h" #include "chrome/browser/prefs/pref_value_store.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/prefs/testing_pref_store.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/json_pref_store.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_pref_service.h" @@ -182,6 +187,82 @@ TEST(PrefServiceTest, UpdateCommandLinePrefStore) { EXPECT_TRUE(actual_bool_value); } +class PrefServiceUserFilePrefsTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_dir_)); + data_dir_ = data_dir_.AppendASCII("pref_service"); + ASSERT_TRUE(file_util::PathExists(data_dir_)); + } + + void ClearListValue(PrefService* prefs, const char* key) { + ListPrefUpdate updater(prefs, key); + updater->Clear(); + } + + void ClearDictionaryValue(PrefService* prefs, const char* key) { + DictionaryPrefUpdate updater(prefs, key); + updater->Clear(); + } + + // The path to temporary directory used to contain the test operations. + ScopedTempDir temp_dir_; + // The path to the directory where the test data is stored. + FilePath data_dir_; + // A message loop that we can use as the file thread message loop. + MessageLoop message_loop_; +}; + +// Verifies that ListValue and DictionaryValue pref with non emtpy default +// preserves its empty value. +TEST_F(PrefServiceUserFilePrefsTest, PreserveEmptyValue) { + FilePath pref_file = temp_dir_.path().AppendASCII("write.json"); + + ASSERT_TRUE(file_util::CopyFile( + data_dir_.AppendASCII("read.need_empty_value.json"), + pref_file)); + + PrefServiceMockBuilder builder; + builder.WithUserFilePrefs(pref_file, base::MessageLoopProxy::current()); + scoped_ptr<PrefService> prefs(builder.Create()); + + // Register testing prefs. + prefs->RegisterListPref("list", + PrefService::UNSYNCABLE_PREF); + prefs->RegisterDictionaryPref("dict", + PrefService::UNSYNCABLE_PREF); + + base::ListValue* non_empty_list = new base::ListValue; + non_empty_list->Append(base::Value::CreateStringValue("test")); + prefs->RegisterListPref("list_needs_empty_value", + non_empty_list, + PrefService::UNSYNCABLE_PREF); + + base::DictionaryValue* non_empty_dict = new base::DictionaryValue; + non_empty_dict->SetString("dummy", "whatever"); + prefs->RegisterDictionaryPref("dict_needs_empty_value", + non_empty_dict, + PrefService::UNSYNCABLE_PREF); + + // Set all testing prefs to empty. + ClearListValue(prefs.get(), "list"); + ClearListValue(prefs.get(), "list_needs_empty_value"); + ClearDictionaryValue(prefs.get(), "dict"); + ClearDictionaryValue(prefs.get(), "dict_needs_empty_value"); + + // Write to file. + prefs->CommitPendingWrite(); + MessageLoop::current()->RunAllPending(); + + // Compare to expected output. + FilePath golden_output_file = + data_dir_.AppendASCII("write.golden.need_empty_value.json"); + ASSERT_TRUE(file_util::PathExists(golden_output_file)); + EXPECT_TRUE(file_util::TextContentsEqual(golden_output_file, pref_file)); +} + class PrefServiceSetValueTest : public testing::Test { protected: static const char kName[]; diff --git a/chrome/browser/prefs/testing_pref_store.cc b/chrome/browser/prefs/testing_pref_store.cc index 8948598..f7ce0f4 100644 --- a/chrome/browser/prefs/testing_pref_store.cc +++ b/chrome/browser/prefs/testing_pref_store.cc @@ -53,6 +53,9 @@ void TestingPrefStore::RemoveValue(const std::string& key) { NotifyPrefValueChanged(key); } +void TestingPrefStore::MarkNeedsEmptyValue(const std::string& key) { +} + bool TestingPrefStore::ReadOnly() const { return read_only_; } diff --git a/chrome/browser/prefs/testing_pref_store.h b/chrome/browser/prefs/testing_pref_store.h index 85965da..9cda821 100644 --- a/chrome/browser/prefs/testing_pref_store.h +++ b/chrome/browser/prefs/testing_pref_store.h @@ -38,6 +38,7 @@ class TestingPrefStore : public PersistentPrefStore { virtual void SetValueSilently(const std::string& key, base::Value* value) OVERRIDE; virtual void RemoveValue(const std::string& key) OVERRIDE; + virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; virtual bool ReadOnly() const OVERRIDE; virtual PrefReadError GetReadError() const OVERRIDE; virtual PersistentPrefStore::PrefReadError ReadPrefs() OVERRIDE; diff --git a/chrome/browser/ui/views/ash/launcher/chrome_launcher_delegate.cc b/chrome/browser/ui/views/ash/launcher/chrome_launcher_delegate.cc index 0eb6e30..d7179f6 100644 --- a/chrome/browser/ui/views/ash/launcher/chrome_launcher_delegate.cc +++ b/chrome/browser/ui/views/ash/launcher/chrome_launcher_delegate.cc @@ -140,19 +140,31 @@ ChromeLauncherDelegate::~ChromeLauncherDelegate() { } void ChromeLauncherDelegate::Init() { - const base::ListValue* pinned_apps = - profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); - - // Use default pinned apps only when current pinned apps list is empty and - // kUseDefaultPinnedApps is true. - // TODO(xiyuan): Remove this after http://crbug.com/122679 is resolved. - scoped_ptr<base::ListValue> default_pinned_apps; - if (pinned_apps->empty() && - profile_->GetPrefs()->GetBoolean(prefs::kUseDefaultPinnedApps)) { - default_pinned_apps.reset(CreateDefaultPinnedAppsList()); - pinned_apps = default_pinned_apps.get(); + // TODO(xiyuan): Remove migration code and kUseDefaultPinnedApp after M20. + // Migration cases: + // - Users that unpin all apps: + // - have default pinned apps + // - kUseDefaultPinnedApps set to false + // Migrate them by setting an empty list for kPinnedLauncherApps. + // + // - Users that have customized pinned apps: + // - have non-default non-empty pinned apps list + // - kUseDefaultPinnedApps set to false + // Nothing needs to be done because customized pref overrides default. + // + // - Users that have default apps (i.e. new user or never pin/unpin): + // - have default pinned apps + // - kUseDefaultPinnedApps is still true + // Nothing needs to be done because they should get the default. + if (profile_->GetPrefs()->FindPreference( + prefs::kPinnedLauncherApps)->IsDefaultValue() && + !profile_->GetPrefs()->GetBoolean(prefs::kUseDefaultPinnedApps)) { + ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); + updater.Get()->Clear(); } + const base::ListValue* pinned_apps = + profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); for (size_t i = 0; i < pinned_apps->GetSize(); ++i) { DictionaryValue* app = NULL; if (pinned_apps->GetDictionary(i, &app)) { @@ -201,6 +213,7 @@ void ChromeLauncherDelegate::RegisterUserPrefs(PrefService* user_prefs) { true, PrefService::SYNCABLE_PREF); user_prefs->RegisterListPref(prefs::kPinnedLauncherApps, + CreateDefaultPinnedAppsList(), PrefService::SYNCABLE_PREF); user_prefs->RegisterStringPref(prefs::kShelfAutoHideBehavior, kShelfAutoHideBehaviorDefault, @@ -573,7 +586,7 @@ void ChromeLauncherDelegate::PersistPinnedState() { profile_->GetPrefs()->SetBoolean(prefs::kUseDefaultPinnedApps, false); ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); - updater.Get()->Clear(); + updater->Clear(); for (size_t i = 0; i < model_->items().size(); ++i) { if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { ash::LauncherID id = model_->items()[i].id; @@ -583,7 +596,7 @@ void ChromeLauncherDelegate::PersistPinnedState() { id_to_item_map_[id].app_id, id_to_item_map_[id].app_type); if (app_value) - updater.Get()->Append(app_value); + updater->Append(app_value); } } } diff --git a/chrome/common/json_pref_store.cc b/chrome/common/json_pref_store.cc index 006ed33..488d7c7 100644 --- a/chrome/common/json_pref_store.cc +++ b/chrome/common/json_pref_store.cc @@ -212,6 +212,10 @@ void JsonPrefStore::RemoveValue(const std::string& key) { ReportValueChanged(key); } +void JsonPrefStore::MarkNeedsEmptyValue(const std::string& key) { + keys_need_empty_value_.insert(key); +} + bool JsonPrefStore::ReadOnly() const { return read_only_; } @@ -310,5 +314,29 @@ bool JsonPrefStore::SerializeData(std::string* output) { JSONStringValueSerializer serializer(output); serializer.set_pretty_print(true); scoped_ptr<DictionaryValue> copy(prefs_->DeepCopyWithoutEmptyChildren()); + + // Iterates |keys_need_empty_value_| and if the key exists in |prefs_|, + // ensure its empty ListValue or DictonaryValue is preserved. + for (std::set<std::string>::const_iterator + it = keys_need_empty_value_.begin(); + it != keys_need_empty_value_.end(); + ++it) { + const std::string& key = *it; + + base::Value* value = NULL; + if (!prefs_->Get(key, &value)) + continue; + + if (value->IsType(base::Value::TYPE_LIST)) { + const base::ListValue* list = NULL; + if (value->GetAsList(&list) && list->empty()) + copy->Set(key, new base::ListValue); + } else if (value->IsType(base::Value::TYPE_DICTIONARY)) { + const base::DictionaryValue* dict = NULL; + if (value->GetAsDictionary(&dict) && dict->empty()) + copy->Set(key, new base::DictionaryValue); + } + } + return serializer.Serialize(*(copy.get())); } diff --git a/chrome/common/json_pref_store.h b/chrome/common/json_pref_store.h index e902751..21198cc 100644 --- a/chrome/common/json_pref_store.h +++ b/chrome/common/json_pref_store.h @@ -6,6 +6,7 @@ #define CHROME_COMMON_JSON_PREF_STORE_H_ #pragma once +#include <set> #include <string> #include "base/basictypes.h" @@ -49,6 +50,7 @@ class JsonPrefStore : public PersistentPrefStore, virtual void SetValueSilently(const std::string& key, base::Value* value) OVERRIDE; virtual void RemoveValue(const std::string& key) OVERRIDE; + virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; virtual bool ReadOnly() const OVERRIDE; virtual PrefReadError GetReadError() const OVERRIDE; virtual PrefReadError ReadPrefs() OVERRIDE; @@ -83,6 +85,8 @@ class JsonPrefStore : public PersistentPrefStore, bool initialized_; PrefReadError read_error_; + std::set<std::string> keys_need_empty_value_; + DISALLOW_COPY_AND_ASSIGN(JsonPrefStore); }; diff --git a/chrome/common/json_pref_store_unittest.cc b/chrome/common/json_pref_store_unittest.cc index 05d2493..5b5a5f3 100644 --- a/chrome/common/json_pref_store_unittest.cc +++ b/chrome/common/json_pref_store_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -244,3 +244,50 @@ TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) { EXPECT_FALSE(pref_store->ReadOnly()); } + +TEST_F(JsonPrefStoreTest, NeedsEmptyValue) { + FilePath pref_file = temp_dir_.path().AppendASCII("write.json"); + + ASSERT_TRUE(file_util::CopyFile( + data_dir_.AppendASCII("read.need_empty_value.json"), + pref_file)); + + // Test that the persistent value can be loaded. + ASSERT_TRUE(file_util::PathExists(pref_file)); + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore(pref_file, message_loop_proxy_.get()); + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); + ASSERT_FALSE(pref_store->ReadOnly()); + + // The JSON file looks like this: + // { + // "list": [ 1 ], + // "list_needs_empty_value": [ 2 ], + // "dict": { + // "dummy": true, + // }, + // "dict_needs_empty_value": { + // "dummy": true, + // }, + // } + + // Set flag to preserve empty values for the following keys. + pref_store->MarkNeedsEmptyValue("list_needs_empty_value"); + pref_store->MarkNeedsEmptyValue("dict_needs_empty_value"); + + // Set all keys to empty values. + pref_store->SetValue("list", new base::ListValue); + pref_store->SetValue("list_needs_empty_value", new base::ListValue); + pref_store->SetValue("dict", new base::DictionaryValue); + pref_store->SetValue("dict_needs_empty_value", new base::DictionaryValue); + + // Write to file. + pref_store->CommitPendingWrite(); + MessageLoop::current()->RunAllPending(); + + // Compare to expected output. + FilePath golden_output_file = + data_dir_.AppendASCII("write.golden.need_empty_value.json"); + ASSERT_TRUE(file_util::PathExists(golden_output_file)); + EXPECT_TRUE(file_util::TextContentsEqual(golden_output_file, pref_file)); +} diff --git a/chrome/common/persistent_pref_store.h b/chrome/common/persistent_pref_store.h index df5a49a..8b8b87b 100644 --- a/chrome/common/persistent_pref_store.h +++ b/chrome/common/persistent_pref_store.h @@ -65,6 +65,10 @@ class PersistentPrefStore : public PrefStore { // Removes the value for |key|. virtual void RemoveValue(const std::string& key) = 0; + // Marks that the |key| with empty ListValue/DictionaryValue needs to be + // persisted. + virtual void MarkNeedsEmptyValue(const std::string& key) = 0; + // Whether the store is in a pseudo-read-only mode where changes are not // actually persisted to disk. This happens in some cases when there are // read errors during startup. diff --git a/chrome/test/data/pref_service/read.need_empty_value.json b/chrome/test/data/pref_service/read.need_empty_value.json new file mode 100644 index 0000000..48e1749 --- /dev/null +++ b/chrome/test/data/pref_service/read.need_empty_value.json @@ -0,0 +1,10 @@ +{ + "list": [ 1 ], + "list_needs_empty_value": [ 2 ], + "dict": { + "dummy": true + }, + "dict_needs_empty_value": { + "dummy": true + } +} diff --git a/chrome/test/data/pref_service/write.golden.need_empty_value.json b/chrome/test/data/pref_service/write.golden.need_empty_value.json new file mode 100644 index 0000000..fa79590 --- /dev/null +++ b/chrome/test/data/pref_service/write.golden.need_empty_value.json @@ -0,0 +1,6 @@ +{ + "dict_needs_empty_value": { + + }, + "list_needs_empty_value": [ ] +} |