// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/themes/theme_syncable_service.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/time/time.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/themes/theme_service.h" #include "chrome/browser/themes/theme_service_factory.h" #include "chrome/common/extensions/manifest_url_handler.h" #include "chrome/test/base/testing_profile.h" #include "content/public/test/test_browser_thread.h" #include "extensions/browser/extension_prefs.h" #include "extensions/common/extension.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/permissions/api_permission_set.h" #include "extensions/common/permissions/permission_set.h" #include "sync/api/attachments/attachment_id.h" #include "sync/api/fake_sync_change_processor.h" #include "sync/api/sync_change_processor_wrapper_for_test.h" #include "sync/api/sync_error.h" #include "sync/api/sync_error_factory_mock.h" #include "sync/internal_api/public/attachments/attachment_service_proxy_for_test.h" #include "sync/protocol/sync.pb.h" #include "sync/protocol/theme_specifics.pb.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #endif using std::string; namespace { static const char kCustomThemeName[] = "name"; static const char kCustomThemeUrl[] = "http://update.url/foo"; #if defined(OS_WIN) const base::FilePath::CharType kExtensionFilePath[] = FILE_PATH_LITERAL("c:\\foo"); #elif defined(OS_POSIX) const base::FilePath::CharType kExtensionFilePath[] = FILE_PATH_LITERAL("/oo"); #endif class FakeThemeService : public ThemeService { public: FakeThemeService() : using_system_theme_(false), using_default_theme_(false), theme_extension_(NULL), is_dirty_(false) {} // ThemeService implementation virtual void SetTheme(const extensions::Extension* extension) OVERRIDE { is_dirty_ = true; theme_extension_ = extension; using_system_theme_ = false; using_default_theme_ = false; } virtual void UseDefaultTheme() OVERRIDE { is_dirty_ = true; using_default_theme_ = true; using_system_theme_ = false; theme_extension_ = NULL; } virtual void UseSystemTheme() OVERRIDE { is_dirty_ = true; using_system_theme_ = true; using_default_theme_ = false; theme_extension_ = NULL; } virtual bool UsingDefaultTheme() const OVERRIDE { return using_default_theme_; } virtual bool UsingSystemTheme() const OVERRIDE { return using_system_theme_; } virtual string GetThemeID() const OVERRIDE { if (theme_extension_.get()) return theme_extension_->id(); else return std::string(); } const extensions::Extension* theme_extension() const { return theme_extension_.get(); } bool is_dirty() const { return is_dirty_; } void MarkClean() { is_dirty_ = false; } private: bool using_system_theme_; bool using_default_theme_; scoped_refptr theme_extension_; bool is_dirty_; }; KeyedService* BuildMockThemeService(content::BrowserContext* profile) { return new FakeThemeService; } scoped_refptr MakeThemeExtension( const base::FilePath& extension_path, const string& name, extensions::Manifest::Location location, const string& update_url) { base::DictionaryValue source; source.SetString(extensions::manifest_keys::kName, name); source.Set(extensions::manifest_keys::kTheme, new base::DictionaryValue()); source.SetString(extensions::manifest_keys::kUpdateURL, update_url); source.SetString(extensions::manifest_keys::kVersion, "0.0.0.0"); string error; scoped_refptr extension = extensions::Extension::Create( extension_path, location, source, extensions::Extension::NO_FLAGS, &error); EXPECT_TRUE(extension.get()); EXPECT_EQ("", error); return extension; } } // namespace class ThemeSyncableServiceTest : public testing::Test { protected: ThemeSyncableServiceTest() : ui_thread_(content::BrowserThread::UI, &loop_), file_thread_(content::BrowserThread::FILE, &loop_), fake_theme_service_(NULL) {} virtual ~ThemeSyncableServiceTest() {} virtual void SetUp() { profile_.reset(new TestingProfile); fake_theme_service_ = BuildForProfile(profile_.get()); theme_sync_service_.reset(new ThemeSyncableService(profile_.get(), fake_theme_service_)); fake_change_processor_.reset(new syncer::FakeSyncChangeProcessor); SetUpExtension(); } virtual void TearDown() { profile_.reset(); loop_.RunUntilIdle(); } void SetUpExtension() { CommandLine command_line(CommandLine::NO_PROGRAM); extensions::TestExtensionSystem* test_ext_system = static_cast( extensions::ExtensionSystem::Get(profile_.get())); ExtensionService* service = test_ext_system->CreateExtensionService( &command_line, base::FilePath(kExtensionFilePath), false); EXPECT_TRUE(service->extensions_enabled()); service->Init(); loop_.RunUntilIdle(); // Create and add custom theme extension so the ThemeSyncableService can // find it. theme_extension_ = MakeThemeExtension(base::FilePath(kExtensionFilePath), kCustomThemeName, GetThemeLocation(), kCustomThemeUrl); extensions::APIPermissionSet empty_set; extensions::ManifestPermissionSet empty_manifest_permissions; extensions::URLPatternSet empty_extent; scoped_refptr permissions = new extensions::PermissionSet(empty_set, empty_manifest_permissions, empty_extent, empty_extent); extensions::ExtensionPrefs::Get(profile_.get()) ->AddGrantedPermissions(theme_extension_->id(), permissions.get()); service->AddExtension(theme_extension_.get()); ASSERT_EQ(1u, service->extensions()->size()); } // Overridden in PolicyInstalledThemeTest below. virtual extensions::Manifest::Location GetThemeLocation() { return extensions::Manifest::INTERNAL; } FakeThemeService* BuildForProfile(Profile* profile) { return static_cast( ThemeServiceFactory::GetInstance()->SetTestingFactoryAndUse( profile, &BuildMockThemeService)); } syncer::SyncDataList MakeThemeDataList( const sync_pb::ThemeSpecifics& theme_specifics) { syncer::SyncDataList list; sync_pb::EntitySpecifics entity_specifics; entity_specifics.mutable_theme()->CopyFrom(theme_specifics); list.push_back(syncer::SyncData::CreateLocalData( ThemeSyncableService::kCurrentThemeClientTag, ThemeSyncableService::kCurrentThemeNodeTitle, entity_specifics)); return list; } // Needed for setting up extension service. base::MessageLoop loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread file_thread_; #if defined OS_CHROMEOS chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; chromeos::ScopedTestCrosSettings test_cros_settings_; chromeos::ScopedTestUserManager test_user_manager_; #endif scoped_ptr profile_; FakeThemeService* fake_theme_service_; scoped_refptr theme_extension_; scoped_ptr theme_sync_service_; scoped_ptr fake_change_processor_; }; class PolicyInstalledThemeTest : public ThemeSyncableServiceTest { virtual extensions::Manifest::Location GetThemeLocation() OVERRIDE { return extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD; } }; TEST_F(ThemeSyncableServiceTest, AreThemeSpecificsEqual) { sync_pb::ThemeSpecifics a, b; EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); // Custom vs. non-custom. a.set_use_custom_theme(true); EXPECT_FALSE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_FALSE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); // Custom theme equality. b.set_use_custom_theme(true); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); a.set_custom_theme_id("id"); EXPECT_FALSE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_FALSE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); b.set_custom_theme_id("id"); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); a.set_custom_theme_update_url("http://update.url"); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); a.set_custom_theme_name("name"); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); // Non-custom theme equality. a.set_use_custom_theme(false); b.set_use_custom_theme(false); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); a.set_use_system_theme_by_default(true); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_FALSE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); b.set_use_system_theme_by_default(true); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, false)); EXPECT_TRUE(ThemeSyncableService::AreThemeSpecificsEqual(a, b, true)); } TEST_F(ThemeSyncableServiceTest, SetCurrentThemeDefaultTheme) { // Set up theme service to use custom theme. fake_theme_service_->SetTheme(theme_extension_.get()); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(sync_pb::ThemeSpecifics()), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); EXPECT_TRUE(fake_theme_service_->UsingDefaultTheme()); } TEST_F(ThemeSyncableServiceTest, SetCurrentThemeSystemTheme) { sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_system_theme_by_default(true); // Set up theme service to use custom theme. fake_theme_service_->SetTheme(theme_extension_.get()); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(theme_specifics), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); EXPECT_TRUE(fake_theme_service_->UsingSystemTheme()); } TEST_F(ThemeSyncableServiceTest, SetCurrentThemeCustomTheme) { sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_custom_theme(true); theme_specifics.set_custom_theme_id(theme_extension_->id()); theme_specifics.set_custom_theme_name(kCustomThemeName); theme_specifics.set_custom_theme_name(kCustomThemeUrl); // Set up theme service to use default theme. fake_theme_service_->UseDefaultTheme(); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(theme_specifics), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); EXPECT_EQ(fake_theme_service_->theme_extension(), theme_extension_.get()); } TEST_F(ThemeSyncableServiceTest, DontResetThemeWhenSpecificsAreEqual) { // Set up theme service to use default theme and expect no changes. fake_theme_service_->UseDefaultTheme(); fake_theme_service_->MarkClean(); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(sync_pb::ThemeSpecifics()), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); EXPECT_FALSE(fake_theme_service_->is_dirty()); } TEST_F(ThemeSyncableServiceTest, UpdateThemeSpecificsFromCurrentTheme) { // Set up theme service to use custom theme. fake_theme_service_->SetTheme(theme_extension_.get()); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, syncer::SyncDataList(), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); const syncer::SyncChangeList& changes = fake_change_processor_->changes(); ASSERT_EQ(1u, changes.size()); EXPECT_TRUE(changes[0].IsValid()); EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type()); EXPECT_EQ(syncer::THEMES, changes[0].sync_data().GetDataType()); const sync_pb::ThemeSpecifics& theme_specifics = changes[0].sync_data().GetSpecifics().theme(); EXPECT_TRUE(theme_specifics.use_custom_theme()); EXPECT_EQ(theme_extension_->id(), theme_specifics.custom_theme_id()); EXPECT_EQ(theme_extension_->name(), theme_specifics.custom_theme_name()); EXPECT_EQ( extensions::ManifestURL::GetUpdateURL(theme_extension_.get()).spec(), theme_specifics.custom_theme_update_url()); } TEST_F(ThemeSyncableServiceTest, GetAllSyncData) { // Set up theme service to use custom theme. fake_theme_service_->SetTheme(theme_extension_.get()); syncer::SyncDataList data_list = theme_sync_service_->GetAllSyncData(syncer::THEMES); ASSERT_EQ(1u, data_list.size()); const sync_pb::ThemeSpecifics& theme_specifics = data_list[0].GetSpecifics().theme(); EXPECT_TRUE(theme_specifics.use_custom_theme()); EXPECT_EQ(theme_extension_->id(), theme_specifics.custom_theme_id()); EXPECT_EQ(theme_extension_->name(), theme_specifics.custom_theme_name()); EXPECT_EQ( extensions::ManifestURL::GetUpdateURL(theme_extension_.get()).spec(), theme_specifics.custom_theme_update_url()); } TEST_F(ThemeSyncableServiceTest, ProcessSyncThemeChange) { // Set up theme service to use default theme. fake_theme_service_->UseDefaultTheme(); fake_theme_service_->MarkClean(); // Start syncing. syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(sync_pb::ThemeSpecifics()), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); // Don't expect theme change initially because specifics are equal. EXPECT_FALSE(fake_theme_service_->is_dirty()); // Change specifics to use custom theme and update. sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_custom_theme(true); theme_specifics.set_custom_theme_id(theme_extension_->id()); theme_specifics.set_custom_theme_name(kCustomThemeName); theme_specifics.set_custom_theme_name(kCustomThemeUrl); sync_pb::EntitySpecifics entity_specifics; entity_specifics.mutable_theme()->CopyFrom(theme_specifics); syncer::SyncChangeList change_list; change_list.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, syncer::SyncData::CreateRemoteData( 1, entity_specifics, base::Time(), syncer::AttachmentIdList(), syncer::AttachmentServiceProxyForTest::Create()))); error = theme_sync_service_->ProcessSyncChanges(FROM_HERE, change_list); EXPECT_FALSE(error.IsSet()) << error.message(); EXPECT_EQ(fake_theme_service_->theme_extension(), theme_extension_.get()); } TEST_F(ThemeSyncableServiceTest, OnThemeChangeByUser) { // Set up theme service to use default theme. fake_theme_service_->UseDefaultTheme(); // Start syncing. syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(sync_pb::ThemeSpecifics()), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); const syncer::SyncChangeList& changes = fake_change_processor_->changes(); EXPECT_EQ(0u, changes.size()); // Change current theme to custom theme and notify theme_sync_service_. fake_theme_service_->SetTheme(theme_extension_.get()); theme_sync_service_->OnThemeChange(); EXPECT_EQ(1u, changes.size()); const sync_pb::ThemeSpecifics& change_specifics = changes[0].sync_data().GetSpecifics().theme(); EXPECT_TRUE(change_specifics.use_custom_theme()); EXPECT_EQ(theme_extension_->id(), change_specifics.custom_theme_id()); EXPECT_EQ(theme_extension_->name(), change_specifics.custom_theme_name()); EXPECT_EQ( extensions::ManifestURL::GetUpdateURL(theme_extension_.get()).spec(), change_specifics.custom_theme_update_url()); } TEST_F(ThemeSyncableServiceTest, StopSync) { // Set up theme service to use default theme. fake_theme_service_->UseDefaultTheme(); // Start syncing. syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(sync_pb::ThemeSpecifics()), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(error.IsSet()) << error.message(); const syncer::SyncChangeList& changes = fake_change_processor_->changes(); EXPECT_EQ(0u, changes.size()); // Stop syncing. theme_sync_service_->StopSyncing(syncer::THEMES); // Change current theme to custom theme and notify theme_sync_service_. // No change is output because sync has stopped. fake_theme_service_->SetTheme(theme_extension_.get()); theme_sync_service_->OnThemeChange(); EXPECT_EQ(0u, changes.size()); // ProcessSyncChanges() should return error when sync has stopped. error = theme_sync_service_->ProcessSyncChanges(FROM_HERE, changes); EXPECT_TRUE(error.IsSet()); EXPECT_EQ(syncer::THEMES, error.model_type()); EXPECT_EQ("Theme syncable service is not started.", error.message()); } TEST_F(ThemeSyncableServiceTest, RestoreSystemThemeBitWhenChangeToCustomTheme) { // Initialize to use system theme. fake_theme_service_->UseDefaultTheme(); sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_system_theme_by_default(true); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(theme_specifics), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); // Change to custom theme and notify theme_sync_service_. // use_system_theme_by_default bit should be preserved. fake_theme_service_->SetTheme(theme_extension_.get()); theme_sync_service_->OnThemeChange(); const syncer::SyncChangeList& changes = fake_change_processor_->changes(); EXPECT_EQ(1u, changes.size()); const sync_pb::ThemeSpecifics& change_specifics = changes[0].sync_data().GetSpecifics().theme(); EXPECT_TRUE(change_specifics.use_system_theme_by_default()); } #if defined(TOOLKIT_GTK) TEST_F(ThemeSyncableServiceTest, GtkUpdateSystemThemeBitWhenChangeBetweenSystemAndDefault) { // Initialize to use native theme. fake_theme_service_->UseSystemTheme(); fake_theme_service_->MarkClean(); sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_system_theme_by_default(true); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(theme_specifics), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_FALSE(fake_theme_service_->is_dirty()); // Change to default theme and notify theme_sync_service_. // use_system_theme_by_default bit should be false. fake_theme_service_->UseDefaultTheme(); theme_sync_service_->OnThemeChange(); syncer::SyncChangeList& changes = fake_change_processor_->changes(); EXPECT_EQ(1u, changes.size()); EXPECT_FALSE(changes[0] .sync_data() .GetSpecifics() .theme() .use_system_theme_by_default()); // Change to native theme and notify theme_sync_service_. // use_system_theme_by_default bit should be true. changes.clear(); fake_theme_service_->UseSystemTheme(); theme_sync_service_->OnThemeChange(); EXPECT_EQ(1u, changes.size()); EXPECT_TRUE(changes[0] .sync_data() .GetSpecifics() .theme() .use_system_theme_by_default()); } #endif #ifndef TOOLKIT_GTK TEST_F(ThemeSyncableServiceTest, NonGtkPreserveSystemThemeBitWhenChangeToDefaultTheme) { // Set up theme service to use default theme. fake_theme_service_->UseDefaultTheme(); // Initialize to use custom theme with use_system_theme_by_default set true. sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_custom_theme(true); theme_specifics.set_custom_theme_id(theme_extension_->id()); theme_specifics.set_custom_theme_name(kCustomThemeName); theme_specifics.set_custom_theme_name(kCustomThemeUrl); theme_specifics.set_use_system_theme_by_default(true); syncer::SyncError error = theme_sync_service_ ->MergeDataAndStartSyncing( syncer::THEMES, MakeThemeDataList(theme_specifics), scoped_ptr( new syncer::SyncChangeProcessorWrapperForTest( fake_change_processor_.get())), scoped_ptr( new syncer::SyncErrorFactoryMock())) .error(); EXPECT_EQ(fake_theme_service_->theme_extension(), theme_extension_.get()); // Change to default theme and notify theme_sync_service_. // use_system_theme_by_default bit should be preserved. fake_theme_service_->UseDefaultTheme(); theme_sync_service_->OnThemeChange(); const syncer::SyncChangeList& changes = fake_change_processor_->changes(); EXPECT_EQ(1u, changes.size()); const sync_pb::ThemeSpecifics& change_specifics = changes[0].sync_data().GetSpecifics().theme(); EXPECT_FALSE(change_specifics.use_custom_theme()); EXPECT_TRUE(change_specifics.use_system_theme_by_default()); } #endif TEST_F(PolicyInstalledThemeTest, InstallThemeByPolicy) { // Set up theme service to use custom theme that was installed by policy. fake_theme_service_->SetTheme(theme_extension_.get()); syncer::SyncDataList data_list = theme_sync_service_->GetAllSyncData(syncer::THEMES); ASSERT_EQ(0u, data_list.size()); }