blob: 9f42ad14f80bfb7be9637b8171fd2cc2c35b40b8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/stl_util.h"
#include "base/string_piece.h"
#include "chrome/browser/prefs/pref_model_associator.h"
#include "chrome/browser/prefs/scoped_user_pref_update.h"
#include "chrome/browser/signin/signin_manager.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/signin/token_service_factory.h"
#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
#include "chrome/browser/sync/glue/generic_change_processor.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/glue/ui_data_type_controller.h"
#include "chrome/browser/sync/profile_sync_test_util.h"
#include "chrome/browser/sync/test_profile_sync_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_pref_service.h"
#include "chrome/test/base/testing_profile.h"
#include "google_apis/gaia/gaia_constants.h"
#include "sync/api/sync_data.h"
#include "sync/internal_api/public/base/model_type.h"
#include "sync/internal_api/public/change_record.h"
#include "sync/internal_api/public/read_node.h"
#include "sync/internal_api/public/read_transaction.h"
#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
#include "sync/protocol/preference_specifics.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::JSONReader;
using browser_sync::GenericChangeProcessor;
using browser_sync::SharedChangeProcessor;
using browser_sync::UIDataTypeController;
using syncer::ChangeRecord;
using testing::_;
using testing::Invoke;
using testing::Return;
typedef std::map<const std::string, const Value*> PreferenceValues;
ACTION_P(CreateAndSaveChangeProcessor, change_processor) {
syncer::UserShare* user_share = arg0->GetUserShare();
*change_processor = new GenericChangeProcessor(arg1, arg2, user_share);
return *change_processor;
}
// TODO(zea): Refactor to remove the ProfileSyncService usage.
class ProfileSyncServicePreferenceTest
: public AbstractProfileSyncServiceTest {
public:
int64 SetSyncedValue(const std::string& name, const Value& value) {
syncer::WriteTransaction trans(FROM_HERE, service_->GetUserShare());
syncer::ReadNode root(&trans);
if (root.InitByTagLookup(syncer::ModelTypeToRootTag(
syncer::PREFERENCES)) != syncer::BaseNode::INIT_OK) {
return syncer::kInvalidId;
}
syncer::WriteNode tag_node(&trans);
syncer::WriteNode node(&trans);
if (tag_node.InitByClientTagLookup(syncer::PREFERENCES, name) ==
syncer::BaseNode::INIT_OK) {
return WriteSyncedValue(name, value, &tag_node);
}
syncer::WriteNode::InitUniqueByCreationResult result =
node.InitUniqueByCreation(syncer::PREFERENCES, root, name);
if (result == syncer::WriteNode::INIT_SUCCESS)
return WriteSyncedValue(name, value, &node);
return syncer::kInvalidId;
}
protected:
ProfileSyncServicePreferenceTest()
: example_url0_("http://example.com/0"),
example_url1_("http://example.com/1"),
example_url2_("http://example.com/2"),
not_synced_preference_name_("nonsense_pref_name"),
not_synced_preference_default_value_("default"),
non_default_charset_value_("foo") {}
virtual void SetUp() {
AbstractProfileSyncServiceTest::SetUp();
profile_.reset(new TestingProfile());
profile_->CreateRequestContext();
prefs_ = profile_->GetTestingPrefService();
prefs_->RegisterStringPref(not_synced_preference_name_.c_str(),
not_synced_preference_default_value_,
PrefService::UNSYNCABLE_PREF);
}
virtual void TearDown() {
service_.reset();
profile_.reset();
AbstractProfileSyncServiceTest::TearDown();
}
bool StartSyncService(const base::Closure& callback,
bool will_fail_association) {
if (service_.get())
return false;
SigninManager* signin = SigninManagerFactory::GetForProfile(profile_.get());
signin->SetAuthenticatedUsername("test");
ProfileSyncComponentsFactoryMock* factory =
new ProfileSyncComponentsFactoryMock();
service_.reset(new TestProfileSyncService(
factory,
profile_.get(),
signin,
ProfileSyncService::AUTO_START,
false,
callback));
pref_sync_service_ = reinterpret_cast<PrefModelAssociator*>(
prefs_->GetSyncableService());
if (!pref_sync_service_)
return false;
EXPECT_CALL(*factory, GetSyncableServiceForType(syncer::PREFERENCES)).
WillOnce(Return(pref_sync_service_->AsWeakPtr()));
EXPECT_CALL(*factory, CreateDataTypeManager(_, _, _)).
WillOnce(ReturnNewDataTypeManager());
dtc_ = new UIDataTypeController(syncer::PREFERENCES,
factory,
profile_.get(),
service_.get());
EXPECT_CALL(*factory, CreateSharedChangeProcessor()).
WillOnce(Return(new SharedChangeProcessor()));
EXPECT_CALL(*factory, CreateGenericChangeProcessor(_, _, _)).
WillOnce(CreateAndSaveChangeProcessor(&change_processor_));
service_->RegisterDataTypeController(dtc_);
TokenServiceFactory::GetForProfile(profile_.get())->IssueAuthTokenForTest(
GaiaConstants::kSyncService, "token");
service_->Initialize();
MessageLoop::current()->Run();
return true;
}
const Value& GetPreferenceValue(const std::string& name) {
const PrefService::Preference* preference =
prefs_->FindPreference(name.c_str());
return *preference->GetValue();
}
// Caller gets ownership of the returned value.
const Value* GetSyncedValue(const std::string& name) {
syncer::ReadTransaction trans(FROM_HERE, service_->GetUserShare());
syncer::ReadNode node(&trans);
if (node.InitByClientTagLookup(syncer::PREFERENCES, name) !=
syncer::BaseNode::INIT_OK) {
return NULL;
}
const sync_pb::PreferenceSpecifics& specifics(
node.GetEntitySpecifics().preference());
return base::JSONReader::Read(specifics.value());
}
int64 WriteSyncedValue(const std::string& name,
const Value& value,
syncer::WriteNode* node) {
syncer::SyncData sync_data;
if (!PrefModelAssociator::CreatePrefSyncData(name,
value,
&sync_data)) {
return syncer::kInvalidId;
}
node->SetEntitySpecifics(sync_data.GetSpecifics());
return node->GetId();
}
bool IsSynced(const std::string& pref_name) {
return pref_sync_service_->registered_preferences().count(pref_name) > 0;
}
std::string ValueString(const Value& value) {
std::string serialized;
JSONStringValueSerializer json(&serialized);
json.Serialize(value);
return serialized;
}
scoped_ptr<TestingProfile> profile_;
TestingPrefService* prefs_;
UIDataTypeController* dtc_;
PrefModelAssociator* pref_sync_service_;
GenericChangeProcessor* change_processor_;
std::string example_url0_;
std::string example_url1_;
std::string example_url2_;
std::string not_synced_preference_name_;
std::string not_synced_preference_default_value_;
std::string non_default_charset_value_;
};
class AddPreferenceEntriesHelper {
public:
AddPreferenceEntriesHelper(ProfileSyncServicePreferenceTest* test,
const PreferenceValues& entries)
: ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
base::Bind(
&AddPreferenceEntriesHelper::AddPreferenceEntriesCallback,
base::Unretained(this), test, entries))),
success_(false) {
}
const base::Closure& callback() const { return callback_; }
bool success() { return success_; }
private:
void AddPreferenceEntriesCallback(ProfileSyncServicePreferenceTest* test,
const PreferenceValues& entries) {
if (!test->CreateRoot(syncer::PREFERENCES))
return;
for (PreferenceValues::const_iterator i = entries.begin();
i != entries.end(); ++i) {
if (test->SetSyncedValue(i->first, *i->second) == syncer::kInvalidId)
return;
}
success_ = true;
}
base::Closure callback_;
bool success_;
};
TEST_F(ProfileSyncServicePreferenceTest, CreatePrefSyncData) {
prefs_->SetString(prefs::kHomePage, example_url0_);
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
syncer::SyncData sync_data;
EXPECT_TRUE(PrefModelAssociator::CreatePrefSyncData(pref->name(),
*pref->GetValue(), &sync_data));
EXPECT_EQ(std::string(prefs::kHomePage), sync_data.GetTag());
const sync_pb::PreferenceSpecifics& specifics(sync_data.GetSpecifics().
preference());
EXPECT_EQ(std::string(prefs::kHomePage), specifics.name());
scoped_ptr<Value> value(base::JSONReader::Read(specifics.value()));
EXPECT_TRUE(pref->GetValue()->Equals(value.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationDoNotSyncDefaults) {
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
EXPECT_TRUE(pref->IsDefaultValue());
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
EXPECT_TRUE(IsSynced(prefs::kHomePage));
EXPECT_TRUE(pref->IsDefaultValue());
EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL);
EXPECT_TRUE(GetSyncedValue(not_synced_preference_name_) == NULL);
}
TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationEmptyCloud) {
prefs_->SetString(prefs::kHomePage, example_url0_);
{
ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup);
ListValue* url_list = update.Get();
url_list->Append(Value::CreateStringValue(example_url0_));
url_list->Append(Value::CreateStringValue(example_url1_));
}
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<const Value> value(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(value.get());
EXPECT_TRUE(GetPreferenceValue(prefs::kHomePage).Equals(value.get()));
value.reset(GetSyncedValue(prefs::kURLsToRestoreOnStartup));
ASSERT_TRUE(value.get());
EXPECT_TRUE(
GetPreferenceValue(prefs::kURLsToRestoreOnStartup).Equals(value.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationCloudHasData) {
prefs_->SetString(prefs::kHomePage, example_url0_);
{
ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup);
ListValue* url_list = update.Get();
url_list->Append(Value::CreateStringValue(example_url0_));
url_list->Append(Value::CreateStringValue(example_url1_));
}
PreferenceValues cloud_data;
cloud_data[prefs::kHomePage] = Value::CreateStringValue(example_url1_);
ListValue* urls_to_restore = new ListValue;
urls_to_restore->Append(Value::CreateStringValue(example_url1_));
urls_to_restore->Append(Value::CreateStringValue(example_url2_));
cloud_data[prefs::kURLsToRestoreOnStartup] = urls_to_restore;
cloud_data[prefs::kDefaultCharset] =
Value::CreateStringValue(non_default_charset_value_);
AddPreferenceEntriesHelper helper(this, cloud_data);
ASSERT_TRUE(StartSyncService(helper.callback(), false));
ASSERT_TRUE(helper.success());
scoped_ptr<const Value> value(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(value.get());
std::string string_value;
EXPECT_TRUE(static_cast<const StringValue*>(value.get())->
GetAsString(&string_value));
EXPECT_EQ(example_url1_, string_value);
EXPECT_EQ(example_url1_, prefs_->GetString(prefs::kHomePage));
scoped_ptr<ListValue> expected_urls(new ListValue);
expected_urls->Append(Value::CreateStringValue(example_url1_));
expected_urls->Append(Value::CreateStringValue(example_url2_));
expected_urls->Append(Value::CreateStringValue(example_url0_));
value.reset(GetSyncedValue(prefs::kURLsToRestoreOnStartup));
ASSERT_TRUE(value.get());
EXPECT_TRUE(value->Equals(expected_urls.get()));
EXPECT_TRUE(GetPreferenceValue(prefs::kURLsToRestoreOnStartup).
Equals(expected_urls.get()));
value.reset(GetSyncedValue(prefs::kDefaultCharset));
ASSERT_TRUE(value.get());
EXPECT_TRUE(static_cast<const StringValue*>(value.get())->
GetAsString(&string_value));
EXPECT_EQ(non_default_charset_value_, string_value);
EXPECT_EQ(non_default_charset_value_,
prefs_->GetString(prefs::kDefaultCharset));
STLDeleteValues(&cloud_data);
}
TEST_F(ProfileSyncServicePreferenceTest, FailModelAssociation) {
ASSERT_TRUE(StartSyncService(base::Closure(), true));
EXPECT_TRUE(service_->HasUnrecoverableError());
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithDefaultValue) {
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
EXPECT_TRUE(pref->IsDefaultValue());
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
profile_->GetPrefs()->Set(prefs::kHomePage, *expected);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(actual.get());
EXPECT_TRUE(expected->Equals(actual.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithValue) {
profile_->GetPrefs()->SetString(prefs::kHomePage, example_url0_);
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url1_));
profile_->GetPrefs()->Set(prefs::kHomePage, *expected);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(actual.get());
EXPECT_TRUE(expected->Equals(actual.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionUpdate) {
profile_->GetPrefs()->SetString(prefs::kHomePage, example_url0_);
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url1_));
int64 node_id = SetSyncedValue(prefs::kHomePage, *expected);
ASSERT_NE(node_id, syncer::kInvalidId);
{
syncer::WriteTransaction trans(FROM_HERE, service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(
&trans,
ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
node_id, ChangeRecord::ACTION_UPDATE));
}
change_processor_->CommitChangesFromSyncModel();
const Value& actual = GetPreferenceValue(prefs::kHomePage);
EXPECT_TRUE(expected->Equals(&actual));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionAdd) {
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
int64 node_id = SetSyncedValue(prefs::kHomePage, *expected);
ASSERT_NE(node_id, syncer::kInvalidId);
{
syncer::WriteTransaction trans(FROM_HERE, service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(
&trans,
ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
node_id, ChangeRecord::ACTION_ADD));
}
change_processor_->CommitChangesFromSyncModel();
const Value& actual = GetPreferenceValue(prefs::kHomePage);
EXPECT_TRUE(expected->Equals(&actual));
EXPECT_EQ(1U,
pref_sync_service_->registered_preferences().count(prefs::kHomePage));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeUnknownPreference) {
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
int64 node_id = SetSyncedValue("unknown preference", *expected);
ASSERT_NE(node_id, syncer::kInvalidId);
{
syncer::WriteTransaction trans(FROM_HERE, service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(
&trans,
ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
node_id, ChangeRecord::ACTION_UPDATE));
}
change_processor_->CommitChangesFromSyncModel();
// Nothing interesting happens on the client when it gets an update
// of an unknown preference. We just should not crash.
}
TEST_F(ProfileSyncServicePreferenceTest, ManagedPreferences) {
// Make the homepage preference managed.
scoped_ptr<Value> managed_value(
Value::CreateStringValue("http://example.com"));
prefs_->SetManagedPref(prefs::kHomePage, managed_value->DeepCopy());
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
// Changing the homepage preference should not sync anything.
scoped_ptr<Value> user_value(
Value::CreateStringValue("http://chromium..com"));
prefs_->SetUserPref(prefs::kHomePage, user_value->DeepCopy());
EXPECT_EQ(NULL, GetSyncedValue(prefs::kHomePage));
// An incoming sync transaction should change the user value, not the managed
// value.
scoped_ptr<Value> sync_value(
Value::CreateStringValue("http://crbug.com"));
int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value);
ASSERT_NE(node_id, syncer::kInvalidId);
{
syncer::WriteTransaction trans(FROM_HERE, service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(
&trans,
ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
node_id, ChangeRecord::ACTION_UPDATE));
}
change_processor_->CommitChangesFromSyncModel();
EXPECT_TRUE(managed_value->Equals(prefs_->GetManagedPref(prefs::kHomePage)));
EXPECT_TRUE(sync_value->Equals(prefs_->GetUserPref(prefs::kHomePage)));
}
TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedPreferences) {
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> initial_value(
Value::CreateStringValue("http://example.com/initial"));
profile_->GetPrefs()->Set(prefs::kHomePage, *initial_value);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
EXPECT_TRUE(initial_value->Equals(actual.get()));
// Switch kHomePage to managed and set a different value.
scoped_ptr<Value> managed_value(
Value::CreateStringValue("http://example.com/managed"));
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kHomePage, managed_value->DeepCopy());
// The pref value should be the one dictated by policy.
EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
// Switch kHomePage back to unmanaged.
profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage);
// The original value should be picked up.
EXPECT_TRUE(initial_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
}
TEST_F(ProfileSyncServicePreferenceTest,
DynamicManagedPreferencesWithSyncChange) {
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
scoped_ptr<Value> initial_value(
Value::CreateStringValue("http://example.com/initial"));
profile_->GetPrefs()->Set(prefs::kHomePage, *initial_value);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
EXPECT_TRUE(initial_value->Equals(actual.get()));
// Switch kHomePage to managed and set a different value.
scoped_ptr<Value> managed_value(
Value::CreateStringValue("http://example.com/managed"));
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kHomePage, managed_value->DeepCopy());
// Change the sync value.
scoped_ptr<Value> sync_value(
Value::CreateStringValue("http://example.com/sync"));
int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value);
ASSERT_NE(node_id, syncer::kInvalidId);
{
syncer::WriteTransaction trans(FROM_HERE, service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(
&trans,
ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
node_id, ChangeRecord::ACTION_ADD));
}
change_processor_->CommitChangesFromSyncModel();
// The pref value should still be the one dictated by policy.
EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
// Switch kHomePage back to unmanaged.
profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage);
// Sync value should be picked up.
EXPECT_TRUE(sync_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
}
TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedDefaultPreferences) {
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
EXPECT_TRUE(pref->IsDefaultValue());
CreateRootHelper create_root(this, syncer::PREFERENCES);
ASSERT_TRUE(StartSyncService(create_root.callback(), false));
ASSERT_TRUE(create_root.success());
EXPECT_TRUE(IsSynced(prefs::kHomePage));
EXPECT_TRUE(pref->IsDefaultValue());
EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL);
// Switch kHomePage to managed and set a different value.
scoped_ptr<Value> managed_value(
Value::CreateStringValue("http://example.com/managed"));
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kHomePage, managed_value->DeepCopy());
// The pref value should be the one dictated by policy.
EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
EXPECT_FALSE(pref->IsDefaultValue());
// There should be no synced value.
EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL);
// Switch kHomePage back to unmanaged.
profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage);
// The original value should be picked up.
EXPECT_TRUE(pref->IsDefaultValue());
// There should still be no synced value.
EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL);
}