diff options
author | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-29 20:04:05 +0000 |
---|---|---|
committer | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-29 20:04:05 +0000 |
commit | 60cab460a9f891d34efacddd3efc10d89279b6d6 (patch) | |
tree | 5edbf5cafb368c09a386bf3dcf122077d067c396 /chrome/browser/sync | |
parent | c5e401ad5812699fafaf41492015eb26174084ed (diff) | |
download | chromium_src-60cab460a9f891d34efacddd3efc10d89279b6d6.zip chromium_src-60cab460a9f891d34efacddd3efc10d89279b6d6.tar.gz chromium_src-60cab460a9f891d34efacddd3efc10d89279b6d6.tar.bz2 |
Implemented initial version of themes syncing (behind a flag).
BUG=32414
TEST=manual,unittests,trybot
Review URL: http://codereview.chromium.org/1399001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42984 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync')
23 files changed, 1394 insertions, 1 deletions
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index d9f48bb..6a3f822 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -52,6 +52,7 @@ #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" #include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/sessions/sync_session_context.h" #include "chrome/browser/sync/syncable/directory_manager.h" @@ -483,6 +484,11 @@ const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const { return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference); } +const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const { + DCHECK(GetModelType() == syncable::THEMES); + return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::theme); +} + const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { DCHECK(GetModelType() == syncable::TYPED_URLS); return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::typed_url); @@ -553,6 +559,12 @@ void WriteNode::SetPreferenceSpecifics( PutPreferenceSpecificsAndMarkForSyncing(new_value); } +void WriteNode::SetThemeSpecifics( + const sync_pb::ThemeSpecifics& new_value) { + DCHECK(GetModelType() == syncable::THEMES); + PutThemeSpecificsAndMarkForSyncing(new_value); +} + void WriteNode::PutPreferenceSpecificsAndMarkForSyncing( const sync_pb::PreferenceSpecifics& new_value) { sync_pb::EntitySpecifics entity_specifics; @@ -566,6 +578,13 @@ void WriteNode::SetTypedUrlSpecifics( PutTypedUrlSpecificsAndMarkForSyncing(new_value); } +void WriteNode::PutThemeSpecificsAndMarkForSyncing( + const sync_pb::ThemeSpecifics& new_value) { + sync_pb::EntitySpecifics entity_specifics; + entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value); + PutSpecificsAndMarkForSyncing(entity_specifics); +} + void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing( const sync_pb::TypedUrlSpecifics& new_value) { sync_pb::EntitySpecifics entity_specifics; diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 98aeeb9..f108b26 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -76,6 +76,7 @@ class AutofillSpecifics; class BookmarkSpecifics; class EntitySpecifics; class PreferenceSpecifics; +class ThemeSpecifics; class TypedUrlSpecifics; } @@ -175,6 +176,10 @@ class BaseNode { // data. Can only be called if GetModelType() == PREFERENCE. const sync_pb::PreferenceSpecifics& GetPreferenceSpecifics() const; + // Getter specific to the THEME datatype. Returns protobuf + // data. Can only be called if GetModelType() == THEME. + const sync_pb::ThemeSpecifics& GetThemeSpecifics() const; + // Getter specific to the TYPED_URLS datatype. Returns protobuf // data. Can only be called if GetModelType() == TYPED_URLS. const sync_pb::TypedUrlSpecifics& GetTypedUrlSpecifics() const; @@ -286,6 +291,10 @@ class WriteNode : public BaseNode { // Should only be called if GetModelType() == PREFERENCE. void SetPreferenceSpecifics(const sync_pb::PreferenceSpecifics& specifics); + // Set the theme specifics (name and value). + // Should only be called if GetModelType() == THEME. + void SetThemeSpecifics(const sync_pb::ThemeSpecifics& specifics); + // Set the typed_url specifics (url, title, typed_count, etc). // Should only be called if GetModelType() == TYPED_URLS. void SetTypedUrlSpecifics(const sync_pb::TypedUrlSpecifics& specifics); @@ -315,6 +324,8 @@ class WriteNode : public BaseNode { const sync_pb::BookmarkSpecifics& new_value); void PutPreferenceSpecificsAndMarkForSyncing( const sync_pb::PreferenceSpecifics& new_value); + void PutThemeSpecificsAndMarkForSyncing( + const sync_pb::ThemeSpecifics& new_value); void PutTypedUrlSpecificsAndMarkForSyncing( const sync_pb::TypedUrlSpecifics& new_value); void PutSpecificsAndMarkForSyncing( diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc index a166d11..d3511ef 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl.cc @@ -23,6 +23,7 @@ static const syncable::ModelType kStartOrder[] = { syncable::BOOKMARKS, syncable::PREFERENCES, syncable::AUTOFILL, + syncable::THEMES, syncable::TYPED_URLS, }; diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index e362d09..d342141 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -168,6 +168,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { registrar_.routing_info[syncable::BOOKMARKS] = GROUP_PASSIVE; registrar_.routing_info[syncable::PREFERENCES] = GROUP_PASSIVE; registrar_.routing_info[syncable::AUTOFILL] = GROUP_PASSIVE; + registrar_.routing_info[syncable::THEMES] = GROUP_PASSIVE; registrar_.routing_info[syncable::TYPED_URLS] = GROUP_PASSIVE; core_thread_.message_loop()->PostTask(FROM_HERE, diff --git a/chrome/browser/sync/glue/theme_change_processor.cc b/chrome/browser/sync/glue/theme_change_processor.cc new file mode 100644 index 0000000..757911c --- /dev/null +++ b/chrome/browser/sync/glue/theme_change_processor.cc @@ -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. + +#include "chrome/browser/sync/glue/theme_change_processor.h" + +#include "base/logging.h" +#include "chrome/browser/browser_theme_provider.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/theme_util.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_source.h" + +namespace browser_sync { + +namespace { +std::string GetThemeId(Extension* current_theme) { + if (current_theme) { + DCHECK(current_theme->IsTheme()); + } + return current_theme ? current_theme->id() : "default/system"; +} +} // namespace + +ThemeChangeProcessor::ThemeChangeProcessor( + UnrecoverableErrorHandler* error_handler) + : ChangeProcessor(error_handler), + profile_(NULL) { + DCHECK(error_handler); +} + +ThemeChangeProcessor::~ThemeChangeProcessor() {} + +void ThemeChangeProcessor::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(running()); + DCHECK(profile_); + Extension* extension = Details<Extension>(details).ptr(); + switch (type.value) { + case NotificationType::BROWSER_THEME_CHANGED: + // We pay attention to this notification only when it signifies + // that the user has set the current theme to the system theme or + // default theme. If the user set the current theme to a custom + // theme, the theme isn't actually loaded until after this + // notification. + LOG(INFO) << "Got BROWSER_THEME_CHANGED notification for theme " + << GetThemeId(extension); + DCHECK_EQ(Source<BrowserThemeProvider>(source).ptr(), + profile_->GetThemeProvider()); + if (extension != NULL) { + DCHECK(extension->IsTheme()); + DCHECK_EQ(extension->id(), profile_->GetThemeProvider()->GetThemeID()); + return; + } + break; + case NotificationType::EXTENSION_LOADED: + // We pay attention to this notification only when it signifies + // that a theme extension has been loaded because that means that + // the user set the current theme to a custom theme and it has + // successfully installed. + DCHECK_EQ(Source<Profile>(source).ptr(), profile_); + CHECK(extension); + if (!extension->IsTheme()) { + return; + } + LOG(INFO) << "Got EXTENSION_LOADED notification for theme " + << extension->id(); + DCHECK_EQ(extension->id(), profile_->GetThemeProvider()->GetThemeID()); + DCHECK_EQ(extension, profile_->GetTheme()); + break; + case NotificationType::EXTENSION_UNLOADED: + // We pay attention to this notification only when it signifies + // that a theme extension has been unloaded because that means + // that the user set the current theme to a custom theme and then + // changed his mind and undid it (reverting to the previous + // theme). + DCHECK_EQ(Source<Profile>(source).ptr(), profile_); + CHECK(extension); + if (!extension->IsTheme()) { + return; + } + LOG(INFO) << "Got EXTENSION_UNLOADED notification for theme " + << extension->id(); + extension = profile_->GetTheme(); + break; + default: + LOG(DFATAL) << "Unexpected notification received: " << type.value; + break; + } + + DCHECK_EQ(extension, profile_->GetTheme()); + if (extension) { + DCHECK(extension->IsTheme()); + } + LOG(INFO) << "Theme changed to " << GetThemeId(extension); + + // Here, we know that a theme is being set; the theme is a custom + // theme iff extension is non-NULL. + + sync_api::WriteTransaction trans(share_handle()); + sync_api::WriteNode node(&trans); + if (!node.InitByClientTagLookup(syncable::THEMES, + kCurrentThemeClientTag)) { + LOG(ERROR) << "Could not create node with client tag: " + << kCurrentThemeClientTag; + error_handler()->OnUnrecoverableError(); + return; + } + + sync_pb::ThemeSpecifics old_theme_specifics = node.GetThemeSpecifics(); + // Make sure to base new_theme_specifics on old_theme_specifics so + // we preserve the state of use_system_theme_by_default. + sync_pb::ThemeSpecifics new_theme_specifics = old_theme_specifics; + GetThemeSpecificsFromCurrentTheme(profile_, &new_theme_specifics); + // Do a write only if something actually changed so as to guard + // against cycles. + if (!AreThemeSpecificsEqual(old_theme_specifics, new_theme_specifics)) { + node.SetThemeSpecifics(new_theme_specifics); + } + return; +} + +void ThemeChangeProcessor::ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count) { + if (!running()) { + return; + } + StopObserving(); + ApplyChangesFromSyncModelHelper(trans, changes, change_count); + StartObserving(); +} + +void ThemeChangeProcessor::StartImpl(Profile* profile) { + DCHECK(profile); + profile_ = profile; + StartObserving(); +} + +void ThemeChangeProcessor::StopImpl() { + StopObserving(); + profile_ = NULL; +} + +void ThemeChangeProcessor::ApplyChangesFromSyncModelHelper( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count) { + if (change_count != 1) { + LOG(ERROR) << "Unexpected number of theme changes"; + error_handler()->OnUnrecoverableError(); + return; + } + const sync_api::SyncManager::ChangeRecord& change = changes[0]; + if (change.action != sync_api::SyncManager::ChangeRecord::ACTION_UPDATE) { + LOG(ERROR) << "Unexpected change.action " << change.action; + error_handler()->OnUnrecoverableError(); + return; + } + sync_api::ReadNode node(trans); + if (!node.InitByIdLookup(change.id)) { + LOG(ERROR) << "Theme node lookup failed"; + error_handler()->OnUnrecoverableError(); + return; + } + DCHECK_EQ(node.GetModelType(), syncable::THEMES); + DCHECK(profile_); + SetCurrentThemeFromThemeSpecificsIfNecessary( + node.GetThemeSpecifics(), profile_); +} + +void ThemeChangeProcessor::StartObserving() { + DCHECK(profile_); + LOG(INFO) << "Observing BROWSER_THEME_CHANGED, EXTENSION_LOADED, " + << "and EXTENSION_UNLOADED"; + notification_registrar_.Add( + this, NotificationType::BROWSER_THEME_CHANGED, + Source<BrowserThemeProvider>(profile_->GetThemeProvider())); + notification_registrar_.Add( + this, NotificationType::EXTENSION_LOADED, + Source<Profile>(profile_)); + notification_registrar_.Add( + this, NotificationType::EXTENSION_UNLOADED, + Source<Profile>(profile_)); +} + +void ThemeChangeProcessor::StopObserving() { + DCHECK(profile_); + LOG(INFO) << "Unobserving all notifications"; + notification_registrar_.RemoveAll(); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/theme_change_processor.h b/chrome/browser/sync/glue/theme_change_processor.h new file mode 100644 index 0000000..3d2922d --- /dev/null +++ b/chrome/browser/sync/glue/theme_change_processor.h @@ -0,0 +1,70 @@ +// 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_SYNC_GLUE_THEME_CHANGE_PROCESSOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_THEME_CHANGE_PROCESSOR_H_ + +#include "base/basictypes.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/change_processor.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/notification_registrar.h" + +class NotificationDetails; +class NotificationSource; +class Profile; + +namespace browser_sync { + +class UnrecoverableErrorHandler; + +// This class is responsible for taking changes from the +// BrowserThemeProvider and applying them to the sync_api 'syncable' +// model, and vice versa. All operations and use of this class are +// from the UI thread. +class ThemeChangeProcessor : public ChangeProcessor, + public NotificationObserver { + public: + explicit ThemeChangeProcessor(UnrecoverableErrorHandler* error_handler); + virtual ~ThemeChangeProcessor(); + + // NotificationObserver implementation. + // BrowserThemeProvider -> sync_api model change application. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // ChangeProcessor implementation. + // sync_api model -> BrowserThemeProvider change application. + virtual void ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count); + + protected: + // ChangeProcessor implementation. + virtual void StartImpl(Profile* profile); + virtual void StopImpl(); + + private: + void ApplyChangesFromSyncModelHelper( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count); + + void StartObserving(); + void StopObserving(); + + NotificationRegistrar notification_registrar_; + // Owner of the BrowserThemeProvider. Non-NULL iff |running()| is + // true. + Profile* profile_; + + DISALLOW_COPY_AND_ASSIGN(ThemeChangeProcessor); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_THEME_CHANGE_PROCESSOR_H_ diff --git a/chrome/browser/sync/glue/theme_data_type_controller.cc b/chrome/browser/sync/glue/theme_data_type_controller.cc new file mode 100644 index 0000000..19f2153 --- /dev/null +++ b/chrome/browser/sync/glue/theme_data_type_controller.cc @@ -0,0 +1,120 @@ +// 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/histogram.h" +#include "base/logging.h" +#include "base/time.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/sync/glue/theme_change_processor.h" +#include "chrome/browser/sync/glue/theme_data_type_controller.h" +#include "chrome/browser/sync/glue/theme_model_associator.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/profile_sync_factory.h" +#include "chrome/browser/sync/unrecoverable_error_handler.h" + +namespace browser_sync { + +ThemeDataTypeController::ThemeDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service) + : profile_sync_factory_(profile_sync_factory), + profile_(profile), + sync_service_(sync_service), + state_(NOT_RUNNING), + unrecoverable_error_detected_(false) { + DCHECK(profile_sync_factory); + DCHECK(sync_service); +} + +ThemeDataTypeController::~ThemeDataTypeController() { +} + +void ThemeDataTypeController::Start(bool merge_allowed, + StartCallback* start_callback) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DCHECK(start_callback); + unrecoverable_error_detected_ = false; + if (state_ != NOT_RUNNING) { + start_callback->Run(BUSY); + delete start_callback; + return; + } + + start_callback_.reset(start_callback); + + profile_->InitExtensions(); + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory_->CreateThemeSyncComponents(sync_service_, + this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); + + bool chrome_has_nodes = false; + if (!model_associator_->ChromeModelHasUserCreatedNodes(&chrome_has_nodes)) { + StartFailed(UNRECOVERABLE_ERROR); + return; + } + bool sync_has_nodes = false; + if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { + StartFailed(UNRECOVERABLE_ERROR); + return; + } + + if (chrome_has_nodes && sync_has_nodes && !merge_allowed) { + StartFailed(NEEDS_MERGE); + return; + } + + base::TimeTicks start_time = base::TimeTicks::Now(); + bool merge_success = model_associator_->AssociateModels(); + UMA_HISTOGRAM_TIMES("Sync.ThemeAssociationTime", + base::TimeTicks::Now() - start_time); + if (!merge_success) { + StartFailed(ASSOCIATION_FAILED); + return; + } + + sync_service_->ActivateDataType(this, change_processor_.get()); + state_ = RUNNING; + FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); +} + +void ThemeDataTypeController::Stop() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + if (unrecoverable_error_detected_) { + FinishStart(UNRECOVERABLE_ERROR); + } + + if (change_processor_ != NULL) + sync_service_->DeactivateDataType(this, change_processor_.get()); + + if (model_associator_ != NULL) + model_associator_->DisassociateModels(); + + change_processor_.reset(); + model_associator_.reset(); + + state_ = NOT_RUNNING; +} + +void ThemeDataTypeController::OnUnrecoverableError() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + unrecoverable_error_detected_ = true; + sync_service_->OnUnrecoverableError(); +} + +void ThemeDataTypeController::FinishStart(StartResult result) { + start_callback_->Run(result); + start_callback_.reset(); +} + +void ThemeDataTypeController::StartFailed(StartResult result) { + model_associator_.reset(); + change_processor_.reset(); + start_callback_->Run(result); + start_callback_.reset(); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/theme_data_type_controller.h b/chrome/browser/sync/glue/theme_data_type_controller.h new file mode 100644 index 0000000..5d020d2 --- /dev/null +++ b/chrome/browser/sync/glue/theme_data_type_controller.h @@ -0,0 +1,81 @@ +// 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_SYNC_GLUE_THEME_DATA_TYPE_CONTROLLER_H_ +#define CHROME_BROWSER_SYNC_GLUE_THEME_DATA_TYPE_CONTROLLER_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/sync/glue/data_type_controller.h" + +class Profile; +class ProfileSyncFactory; +class ProfileSyncService; + +namespace browser_sync { + +class AssociatorInterface; +class ChangeProcessor; + +class ThemeDataTypeController : public DataTypeController { + public: + ThemeDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service); + virtual ~ThemeDataTypeController(); + + // DataTypeController impementation. + virtual void Start(bool merge_allowed, StartCallback* start_callback); + + virtual void Stop(); + + virtual bool enabled() { + return true; + } + + virtual syncable::ModelType type() { + return syncable::THEMES; + } + + virtual browser_sync::ModelSafeGroup model_safe_group() { + return browser_sync::GROUP_UI; + } + + virtual const char* name() const { + // For logging only. + return "theme"; + } + + virtual State state() { + return state_; + } + + // UnrecoverableErrorHandler interface. + virtual void OnUnrecoverableError(); + + private: + // Helper method to run the stashed start callback with a given result. + void FinishStart(StartResult result); + + // Cleans up state and calls callback when start fails. + void StartFailed(StartResult result); + + ProfileSyncFactory* profile_sync_factory_; + Profile* profile_; + ProfileSyncService* sync_service_; + + State state_; + bool unrecoverable_error_detected_; + + scoped_ptr<StartCallback> start_callback_; + scoped_ptr<AssociatorInterface> model_associator_; + scoped_ptr<ChangeProcessor> change_processor_; + + DISALLOW_COPY_AND_ASSIGN(ThemeDataTypeController); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_THEME_DATA_TYPE_CONTROLLER_H_ diff --git a/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc b/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc new file mode 100644 index 0000000..4c04c20 --- /dev/null +++ b/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc @@ -0,0 +1,172 @@ +// 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 "testing/gtest/include/gtest/gtest.h" + +#include "base/callback.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/sync/glue/theme_data_type_controller.h" +#include "chrome/browser/sync/glue/change_processor_mock.h" +#include "chrome/browser/sync/glue/model_associator_mock.h" +#include "chrome/browser/sync/profile_sync_factory_mock.h" +#include "chrome/browser/sync/profile_sync_service_mock.h" +#include "chrome/test/profile_mock.h" + +using browser_sync::ThemeDataTypeController; +using browser_sync::ChangeProcessorMock; +using browser_sync::DataTypeController; +using browser_sync::ModelAssociatorMock; +using testing::_; +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::SetArgumentPointee; + +class StartCallback { + public: + MOCK_METHOD1(Run, void(DataTypeController::StartResult result)); +}; + +class ThemeDataTypeControllerTest : public testing::Test { + public: + ThemeDataTypeControllerTest() + : ui_thread_(ChromeThread::UI, &message_loop_) {} + + virtual void SetUp() { + profile_sync_factory_.reset(new ProfileSyncFactoryMock()); + theme_dtc_ = + new ThemeDataTypeController(profile_sync_factory_.get(), + &profile_, &service_); + } + + protected: + void SetStartExpectations() { + model_associator_ = new ModelAssociatorMock(); + change_processor_ = new ChangeProcessorMock(); + EXPECT_CALL(*profile_sync_factory_, CreateThemeSyncComponents(_, _)). + WillOnce(Return(ProfileSyncFactory::SyncComponents(model_associator_, + change_processor_))); + } + + void SetAssociateExpectations() { + EXPECT_CALL(*model_associator_, ChromeModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); + EXPECT_CALL(*model_associator_, AssociateModels()). + WillRepeatedly(Return(true)); + } + + void SetActivateExpectations() { + EXPECT_CALL(service_, ActivateDataType(_, _)); + } + + void SetStopExpectations() { + EXPECT_CALL(service_, DeactivateDataType(_, _)); + EXPECT_CALL(*model_associator_, DisassociateModels()); + } + + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + scoped_refptr<ThemeDataTypeController> theme_dtc_; + scoped_ptr<ProfileSyncFactoryMock> profile_sync_factory_; + ProfileMock profile_; + ProfileSyncServiceMock service_; + ModelAssociatorMock* model_associator_; + ChangeProcessorMock* change_processor_; + StartCallback start_callback_; +}; + +TEST_F(ThemeDataTypeControllerTest, Start) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(); + EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + theme_dtc_->Start(false, + NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::RUNNING, theme_dtc_->state()); +} + +TEST_F(ThemeDataTypeControllerTest, StartFirstRun) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN)); + theme_dtc_->Start(false, + NewCallback(&start_callback_, &StartCallback::Run)); +} + +TEST_F(ThemeDataTypeControllerTest, StartNeedsMerge) { + SetStartExpectations(); + SetAssociateExpectations(); + EXPECT_CALL(*model_associator_, ChromeModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); + EXPECT_CALL(start_callback_, Run(DataTypeController::NEEDS_MERGE)); + theme_dtc_->Start(false, + NewCallback(&start_callback_, &StartCallback::Run)); +} + +TEST_F(ThemeDataTypeControllerTest, StartMergeAllowed) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(); + EXPECT_CALL(*model_associator_, ChromeModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); + + EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + theme_dtc_->Start(true, + NewCallback(&start_callback_, &StartCallback::Run)); +} + +TEST_F(ThemeDataTypeControllerTest, StartAssociationFailed) { + SetStartExpectations(); + SetAssociateExpectations(); + EXPECT_CALL(*model_associator_, AssociateModels()). + WillRepeatedly(Return(false)); + + EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED)); + theme_dtc_->Start(true, + NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); +} + +TEST_F(ThemeDataTypeControllerTest, + StartAssociationTriggersUnrecoverableError) { + SetStartExpectations(); + // Set up association to fail with an unrecoverable error. + EXPECT_CALL(*model_associator_, ChromeModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); + EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR)); + theme_dtc_->Start(true, + NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); +} + +TEST_F(ThemeDataTypeControllerTest, Stop) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(); + SetStopExpectations(); + + EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); + + EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + theme_dtc_->Start(false, + NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::RUNNING, theme_dtc_->state()); + theme_dtc_->Stop(); + EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); +} diff --git a/chrome/browser/sync/glue/theme_model_associator.cc b/chrome/browser/sync/glue/theme_model_associator.cc new file mode 100644 index 0000000..c4f87ce --- /dev/null +++ b/chrome/browser/sync/glue/theme_model_associator.cc @@ -0,0 +1,111 @@ +// 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/sync/glue/theme_model_associator.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_theme_provider.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/browser/sync/glue/theme_util.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/browser/sync/unrecoverable_error_handler.h" + +namespace browser_sync { + +namespace { + +static const char kThemesTag[] = "google_chrome_themes"; +static const char kCurrentThemeNodeTitle[] = "Current Theme"; + +static const char kNoThemesFolderError[] = + "Server did not create the top-level themes node. We " + "might be running against an out-of-date server."; + +} // namespace + +ThemeModelAssociator::ThemeModelAssociator( + ProfileSyncService* sync_service, + UnrecoverableErrorHandler* error_handler) + : sync_service_(sync_service), + error_handler_(error_handler) { + DCHECK(sync_service_); + DCHECK(error_handler_); +} + +ThemeModelAssociator::~ThemeModelAssociator() {} + +bool ThemeModelAssociator::AssociateModels() { + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode root(&trans); + if (!root.InitByTagLookup(kThemesTag)) { + LOG(ERROR) << kNoThemesFolderError; + error_handler_->OnUnrecoverableError(); + return false; + } + + Profile* profile = sync_service_->profile(); + sync_api::ReadNode node(&trans); + // TODO(akalin): When we have timestamps, we may want to do + // something more intelligent than preferring the sync data over our + // local data. + if (node.InitByClientTagLookup(syncable::THEMES, kCurrentThemeClientTag)) { + // Update the current theme from the sync data. + // TODO(akalin): If the sync data does not have + // use_system_theme_by_default and we do, update that flag on the + // sync data. + SetCurrentThemeFromThemeSpecificsIfNecessary( + node.GetThemeSpecifics(), profile); + } else { + // Set the sync data from the current theme. + sync_api::WriteNode node(&trans); + if (!node.InitUniqueByCreation(syncable::THEMES, root, + kCurrentThemeClientTag)) { + LOG(ERROR) << "Could not create current theme node."; + error_handler_->OnUnrecoverableError(); + return false; + } + node.SetIsFolder(false); + node.SetTitle(UTF8ToWide(kCurrentThemeNodeTitle)); + sync_pb::ThemeSpecifics theme_specifics; + GetThemeSpecificsFromCurrentTheme(profile, &theme_specifics); + node.SetThemeSpecifics(theme_specifics); + } + return true; +} + +bool ThemeModelAssociator::DisassociateModels() { + // We don't maintain any association state, so nothing to do. + return true; +} + +bool ThemeModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { + DCHECK(has_nodes); + *has_nodes = false; + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode root(&trans); + if (!root.InitByTagLookup(kThemesTag)) { + error_handler_->OnUnrecoverableError(); + LOG(ERROR) << kNoThemesFolderError; + return false; + } + // The sync model has user created nodes iff the themes folder has + // any children. + *has_nodes = root.GetFirstChildId() != sync_api::kInvalidId; + return true; +} + +bool ThemeModelAssociator::ChromeModelHasUserCreatedNodes(bool* has_nodes) { + DCHECK(has_nodes); + // Assume the themes model always has user-created nodes. + *has_nodes = true; + return true; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/theme_model_associator.h b/chrome/browser/sync/glue/theme_model_associator.h new file mode 100644 index 0000000..12cbc62 --- /dev/null +++ b/chrome/browser/sync/glue/theme_model_associator.h @@ -0,0 +1,44 @@ +// 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_SYNC_GLUE_THEME_MODEL_ASSOCIATOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_THEME_MODEL_ASSOCIATOR_H_ + +#include "base/basictypes.h" +#include "chrome/browser/sync/glue/model_associator.h" +#include "chrome/browser/sync/syncable/model_type.h" + +class ProfileSyncService; + +namespace browser_sync { + +class UnrecoverableErrorHandler; + +// Contains all logic for associating the Chrome themes model and the +// sync themes model. +class ThemeModelAssociator : public AssociatorInterface { + public: + ThemeModelAssociator(ProfileSyncService* sync_service, + UnrecoverableErrorHandler* error_handler); + virtual ~ThemeModelAssociator(); + + // Used by profile_sync_test_util.h. + static syncable::ModelType model_type() { return syncable::THEMES; } + + // AssociatorInterface implementation. + virtual bool AssociateModels(); + virtual bool DisassociateModels(); + virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes); + virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes); + + private: + ProfileSyncService* sync_service_; + UnrecoverableErrorHandler* error_handler_; + + DISALLOW_COPY_AND_ASSIGN(ThemeModelAssociator); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_THEME_MODEL_ASSOCIATOR_H_ diff --git a/chrome/browser/sync/glue/theme_util.cc b/chrome/browser/sync/glue/theme_util.cc new file mode 100644 index 0000000..ada5f72 --- /dev/null +++ b/chrome/browser/sync/glue/theme_util.cc @@ -0,0 +1,190 @@ +// 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/sync/glue/theme_util.h" + +#include <string> + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/version.h" +#include "chrome/browser/extensions/extension_updater.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/pref_names.h" +#include "googleurl/src/gurl.h" + +namespace browser_sync { + +const char kCurrentThemeClientTag[] = "current_theme"; + +namespace { + +bool IsSystemThemeDistinctFromDefaultTheme() { +#if defined(TOOLKIT_USES_GTK) + return true; +#else + return false; +#endif +} + +} // namespace + +bool AreThemeSpecificsEqual(const sync_pb::ThemeSpecifics& a, + const sync_pb::ThemeSpecifics& b) { + return AreThemeSpecificsEqualHelper( + a, b, IsSystemThemeDistinctFromDefaultTheme()); +} + +bool AreThemeSpecificsEqualHelper( + const sync_pb::ThemeSpecifics& a, + const sync_pb::ThemeSpecifics& b, + bool is_system_theme_distinct_from_default_theme) { + if (a.use_custom_theme() != b.use_custom_theme()) { + return false; + } + + if (a.use_custom_theme()) { + // We're using a custom theme, so simply compare IDs since those + // are guaranteed unique. + return a.custom_theme_id() == b.custom_theme_id(); + } else if (is_system_theme_distinct_from_default_theme) { + // We're not using a custom theme, but we care about system + // vs. default. + return a.use_system_theme_by_default() == b.use_system_theme_by_default(); + } else { + // We're not using a custom theme, and we don't care about system + // vs. default. + return true; + } +} + +void SetCurrentThemeFromThemeSpecifics( + const sync_pb::ThemeSpecifics& theme_specifics, + Profile* profile) { + DCHECK(profile); + if (theme_specifics.use_custom_theme()) { + // TODO(akalin): Figure out what to do about third-party themes + // (i.e., those not on either Google gallery). + std::string id(theme_specifics.custom_theme_id()); + GURL update_url(theme_specifics.custom_theme_update_url()); + LOG(INFO) << "Applying theme " << id << " with update_url " + << update_url; + ExtensionsService* extensions_service = profile->GetExtensionsService(); + CHECK(extensions_service); + Extension* extension = extensions_service->GetExtensionById(id, true); + if (extension) { + if (!extension->IsTheme()) { + LOG(INFO) << "Extension " << id << " is not a theme; aborting"; + return; + } + ExtensionPrefs* extension_prefs = extensions_service->extension_prefs(); + CHECK(extension_prefs); + // TODO(akalin): GetExtensionState() isn't very safe as it + // returns Extension::ENABLED by default; either change it to + // return something else by default or create a separate + // function that does so. + if (extension_prefs->GetExtensionState(extension->id()) != + Extension::ENABLED) { + LOG(INFO) << "Theme " << id << " is not enabled; aborting"; + return; + } + // An enabled theme extension with the given id was found, so + // just set the current theme to it. + // TODO(akalin): Figure out what to do about the fact that this + // applies the theme silently (i.e., without an infobar). + profile->SetTheme(extension); + } else { + // No extension with this id exists -- we must install it; we do + // so by adding it as a pending extension and then triggering an + // auto-update cycle. + scoped_ptr<Version> version(Version::GetVersionFromString("0.0.0.0")); + DCHECK(version.get()); + const bool kIsTheme = true; + // Themes don't need to install silently as they just pop up an + // informational dialog after installation instead of a + // confirmation dialog. + const bool kInstallSilently = false; + extensions_service->AddPendingExtension(id, update_url, *version, + kIsTheme, kInstallSilently); + ExtensionUpdater* extension_updater = extensions_service->updater(); + // Auto-updates should now be on always (see the construction of + // the ExtensionsService in ProfileImpl::InitExtensions()). + if (!extension_updater) { + LOG(DFATAL) << "Extension updater unexpectedly NULL; " + << "auto-updates may be turned off"; + return; + } + extension_updater->CheckNow(); + } + } else if (theme_specifics.use_system_theme_by_default()) { + profile->SetNativeTheme(); + } else { + profile->ClearTheme(); + } +} + +void GetThemeSpecificsFromCurrentTheme( + Profile* profile, + sync_pb::ThemeSpecifics* theme_specifics) { + DCHECK(profile); + const Extension* current_theme = profile->GetTheme(); + if (current_theme) { + DCHECK(current_theme->IsTheme()); + } + bool use_system_theme_by_default = false; +#if defined(TOOLKIT_USES_GTK) + use_system_theme_by_default = + profile->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme); +#endif + GetThemeSpecificsFromCurrentThemeHelper( + current_theme, + IsSystemThemeDistinctFromDefaultTheme(), + use_system_theme_by_default, + theme_specifics); +} + +void GetThemeSpecificsFromCurrentThemeHelper( + const Extension* current_theme, + bool is_system_theme_distinct_from_default_theme, + bool use_system_theme_by_default, + sync_pb::ThemeSpecifics* theme_specifics) { + bool use_custom_theme = (current_theme != NULL); + theme_specifics->set_use_custom_theme(use_custom_theme); + if (is_system_theme_distinct_from_default_theme) { + theme_specifics->set_use_system_theme_by_default( + use_system_theme_by_default); + } else { + DCHECK(!use_system_theme_by_default); + } + if (use_custom_theme) { + DCHECK(current_theme); + DCHECK(current_theme->IsTheme()); + theme_specifics->set_custom_theme_name(current_theme->name()); + theme_specifics->set_custom_theme_id(current_theme->id()); + theme_specifics->set_custom_theme_update_url( + current_theme->update_url().spec()); + } else { + DCHECK(!current_theme); + theme_specifics->clear_custom_theme_name(); + theme_specifics->clear_custom_theme_id(); + theme_specifics->clear_custom_theme_update_url(); + } +} + +void SetCurrentThemeFromThemeSpecificsIfNecessary( + const sync_pb::ThemeSpecifics& theme_specifics, Profile* profile) { + DCHECK(profile); + sync_pb::ThemeSpecifics old_theme_specifics; + GetThemeSpecificsFromCurrentTheme(profile, &old_theme_specifics); + if (!AreThemeSpecificsEqual(old_theme_specifics, theme_specifics)) { + SetCurrentThemeFromThemeSpecifics(theme_specifics, profile); + } +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/theme_util.h b/chrome/browser/sync/glue/theme_util.h new file mode 100644 index 0000000..2821663 --- /dev/null +++ b/chrome/browser/sync/glue/theme_util.h @@ -0,0 +1,55 @@ +// 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_SYNC_GLUE_THEME_UTIL_H_ +#define CHROME_BROWSER_SYNC_GLUE_THEME_UTIL_H_ + +class Extension; +class Profile; + +namespace sync_pb { +class ThemeSpecifics; +} // sync_pb + +namespace browser_sync { + +extern const char kCurrentThemeClientTag[]; + +// Returns true iff two ThemeSpecifics indicate the same theme. +bool AreThemeSpecificsEqual(const sync_pb::ThemeSpecifics& a, + const sync_pb::ThemeSpecifics& b); + +// Exposed only for testing. +bool AreThemeSpecificsEqualHelper( + const sync_pb::ThemeSpecifics& a, + const sync_pb::ThemeSpecifics& b, + bool is_system_theme_distinct_from_default_theme); + +// Sets the current theme from the information in the given +// ThemeSpecifics. +void SetCurrentThemeFromThemeSpecifics( + const sync_pb::ThemeSpecifics& theme_specifics, + Profile* profile); + +// Sets all fields of the given ThemeSpecifics according to the +// current theme. +void GetThemeSpecificsFromCurrentTheme( + Profile* profile, + sync_pb::ThemeSpecifics* theme_specifics); + +// Exposed only for testing. +void GetThemeSpecificsFromCurrentThemeHelper( + const Extension* current_theme, + bool is_system_theme_distinct_from_default_theme, + bool use_system_theme_by_default, + sync_pb::ThemeSpecifics* theme_specifics); + +// Like SetChrrentThemeFromThemeSpecifics() except does nothing if the +// current theme is equivalent to that described by theme_specifics. +void SetCurrentThemeFromThemeSpecificsIfNecessary( + const sync_pb::ThemeSpecifics& theme_specifics, Profile* profile); + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_THEME_UTIL_H_ diff --git a/chrome/browser/sync/glue/theme_util_unittest.cc b/chrome/browser/sync/glue/theme_util_unittest.cc new file mode 100644 index 0000000..fb6641f --- /dev/null +++ b/chrome/browser/sync/glue/theme_util_unittest.cc @@ -0,0 +1,218 @@ +// 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/sync/glue/theme_util.h" + +#include "base/file_path.h" +#include "base/values.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/test/testing_profile.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace browser_sync { + +namespace { + +using ::testing::Return; + +class ThemeUtilTest : public testing::Test { +}; + +void MakeThemeExtension(Extension* extension, + const std::string& name, + const std::string& update_url) { + DictionaryValue source; + source.SetString(extension_manifest_keys::kName, name); + source.Set(extension_manifest_keys::kTheme, new DictionaryValue()); + source.SetString(extension_manifest_keys::kUpdateURL, update_url); + source.SetString(extension_manifest_keys::kVersion, "0.0.0.0"); + std::string error; + EXPECT_TRUE(extension->InitFromValue(source, false, &error)); + EXPECT_EQ("", error); +} + +TEST_F(ThemeUtilTest, AreThemeSpecificsEqualHelper) { + sync_pb::ThemeSpecifics a, b; + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); + + // Custom vs. non-custom. + + a.set_use_custom_theme(true); + EXPECT_FALSE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_FALSE(AreThemeSpecificsEqualHelper(a, b, true)); + + // Custom theme equality. + + b.set_use_custom_theme(true); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); + + a.set_custom_theme_id("id"); + EXPECT_FALSE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_FALSE(AreThemeSpecificsEqualHelper(a, b, true)); + + b.set_custom_theme_id("id"); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); + + a.set_custom_theme_update_url("http://update.url"); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); + + a.set_custom_theme_name("name"); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); + + // Non-custom theme equality. + + a.set_use_custom_theme(false); + b.set_use_custom_theme(false); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); + + a.set_use_system_theme_by_default(true); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_FALSE(AreThemeSpecificsEqualHelper(a, b, true)); + + b.set_use_system_theme_by_default(true); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, false)); + EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); +} + +class MockProfile : public TestingProfile { + public: + MOCK_METHOD0(SetNativeTheme, void()); + MOCK_METHOD0(ClearTheme, void()); + MOCK_METHOD0(GetTheme, Extension*()); +}; + +TEST_F(ThemeUtilTest, SetCurrentThemeDefaultTheme) { + sync_pb::ThemeSpecifics theme_specifics; + + MockProfile mock_profile; + EXPECT_CALL(mock_profile, ClearTheme()).Times(1); + + SetCurrentThemeFromThemeSpecifics(theme_specifics, &mock_profile); +} + +TEST_F(ThemeUtilTest, SetCurrentThemeSystemTheme) { + sync_pb::ThemeSpecifics theme_specifics; + theme_specifics.set_use_system_theme_by_default(true); + + MockProfile mock_profile; + EXPECT_CALL(mock_profile, SetNativeTheme()).Times(1); + + SetCurrentThemeFromThemeSpecifics(theme_specifics, &mock_profile); +} + +// TODO(akalin): Make ExtensionsService/ExtensionUpdater testable +// enough to be able to write a unittest for SetCurrentTheme for a +// custom theme. + +TEST_F(ThemeUtilTest, GetThemeSpecificsHelperNoCustomTheme) { + sync_pb::ThemeSpecifics theme_specifics; + theme_specifics.set_use_custom_theme(true); + theme_specifics.set_use_system_theme_by_default(true); + theme_specifics.set_custom_theme_name("name"); + theme_specifics.set_custom_theme_id("id"); + theme_specifics.set_custom_theme_update_url("updateurl"); + GetThemeSpecificsFromCurrentThemeHelper(NULL, false, false, + &theme_specifics); + + EXPECT_TRUE(theme_specifics.has_use_custom_theme()); + EXPECT_FALSE(theme_specifics.use_custom_theme()); + // Should be preserved since we passed in false for + // is_system_theme_distinct_from_current_theme. + EXPECT_TRUE(theme_specifics.use_system_theme_by_default()); + EXPECT_FALSE(theme_specifics.has_custom_theme_name()); + EXPECT_FALSE(theme_specifics.has_custom_theme_id()); + EXPECT_FALSE(theme_specifics.has_custom_theme_update_url()); +} + +TEST_F(ThemeUtilTest, GetThemeSpecificsHelperNoCustomThemeDistinct) { + sync_pb::ThemeSpecifics theme_specifics; + theme_specifics.set_use_custom_theme(true); + theme_specifics.set_custom_theme_name("name"); + theme_specifics.set_custom_theme_id("id"); + theme_specifics.set_custom_theme_update_url("updateurl"); + GetThemeSpecificsFromCurrentThemeHelper(NULL, true, false, + &theme_specifics); + + EXPECT_TRUE(theme_specifics.has_use_custom_theme()); + EXPECT_FALSE(theme_specifics.use_custom_theme()); + // Should be set since we passed in true for + // is_system_theme_distinct_from_current_theme. + EXPECT_TRUE(theme_specifics.has_use_system_theme_by_default()); + EXPECT_FALSE(theme_specifics.use_system_theme_by_default()); + EXPECT_FALSE(theme_specifics.has_custom_theme_name()); + EXPECT_FALSE(theme_specifics.has_custom_theme_id()); + EXPECT_FALSE(theme_specifics.has_custom_theme_update_url()); +} + +namespace { +#if defined(OS_WIN) +const FilePath::CharType kExtensionFilePath[] = FILE_PATH_LITERAL("c:\\foo"); +#elif defined(OS_POSIX) +const FilePath::CharType kExtensionFilePath[] = FILE_PATH_LITERAL("/oo"); +#endif +} // namespace + +TEST_F(ThemeUtilTest, GetThemeSpecificsHelperCustomTheme) { + sync_pb::ThemeSpecifics theme_specifics; + theme_specifics.set_use_custom_theme(false); + theme_specifics.set_use_system_theme_by_default(true); + FilePath file_path(kExtensionFilePath); + Extension extension(file_path); + const std::string kThemeName("name"); + const std::string kThemeUpdateUrl("http://update.url/foo"); + MakeThemeExtension(&extension, kThemeName, kThemeUpdateUrl); + GetThemeSpecificsFromCurrentThemeHelper(&extension, false, false, + &theme_specifics); + + EXPECT_TRUE(theme_specifics.use_custom_theme()); + EXPECT_TRUE(theme_specifics.use_system_theme_by_default()); + EXPECT_EQ(kThemeName, theme_specifics.custom_theme_name()); + EXPECT_EQ(extension.id(), theme_specifics.custom_theme_id()); + EXPECT_EQ(kThemeUpdateUrl, theme_specifics.custom_theme_update_url()); +} + +TEST_F(ThemeUtilTest, GetThemeSpecificsHelperCustomThemeDistinct) { + sync_pb::ThemeSpecifics theme_specifics; + theme_specifics.set_use_custom_theme(false); + FilePath file_path(kExtensionFilePath); + Extension extension(file_path); + const std::string kThemeName("name"); + const std::string kThemeUpdateUrl("http://update.url/foo"); + MakeThemeExtension(&extension, kThemeName, kThemeUpdateUrl); + GetThemeSpecificsFromCurrentThemeHelper(&extension, true, false, + &theme_specifics); + + EXPECT_TRUE(theme_specifics.use_custom_theme()); + EXPECT_TRUE(theme_specifics.has_use_system_theme_by_default()); + EXPECT_FALSE(theme_specifics.use_system_theme_by_default()); + EXPECT_EQ(kThemeName, theme_specifics.custom_theme_name()); + EXPECT_EQ(extension.id(), theme_specifics.custom_theme_id()); + EXPECT_EQ(kThemeUpdateUrl, theme_specifics.custom_theme_update_url()); +} + +TEST_F(ThemeUtilTest, SetCurrentThemeIfNecessaryDefaultThemeNotNecessary) { + MockProfile mock_profile; + Extension* extension = NULL; + EXPECT_CALL(mock_profile, GetTheme()).WillOnce(Return(extension)); + + // TODO(akalin): Mock out call to GetPrefs() under TOOLKIT_USES_GTK. + + sync_pb::ThemeSpecifics theme_specifics; + SetCurrentThemeFromThemeSpecificsIfNecessary(theme_specifics, + &mock_profile); +} + +} // namespace + +} // namespace browser_sync diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h index ef813d4..67b6296 100644 --- a/chrome/browser/sync/profile_sync_factory.h +++ b/chrome/browser/sync/profile_sync_factory.h @@ -77,6 +77,13 @@ class ProfileSyncFactory { browser_sync::UnrecoverableErrorHandler* error_handler) = 0; // Instantiates both a model associator and change processor for the + // theme data type. The pointers in the return struct are + // owned by the caller. + virtual SyncComponents CreateThemeSyncComponents( + ProfileSyncService* profile_sync_service, + browser_sync::UnrecoverableErrorHandler* error_handler) = 0; + + // Instantiates both a model associator and change processor for the // typed_url data type. The pointers in the return struct are owned // by the caller. virtual SyncComponents CreateTypedUrlSyncComponents( diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index 89df00ba..36e3614 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -16,6 +16,9 @@ #include "chrome/browser/sync/glue/preference_data_type_controller.h" #include "chrome/browser/sync/glue/preference_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/browser/sync/glue/theme_change_processor.h" +#include "chrome/browser/sync/glue/theme_data_type_controller.h" +#include "chrome/browser/sync/glue/theme_model_associator.h" #include "chrome/browser/sync/glue/typed_url_change_processor.h" #include "chrome/browser/sync/glue/typed_url_data_type_controller.h" #include "chrome/browser/sync/glue/typed_url_model_associator.h" @@ -37,6 +40,9 @@ using browser_sync::PreferenceChangeProcessor; using browser_sync::PreferenceDataTypeController; using browser_sync::PreferenceModelAssociator; using browser_sync::SyncBackendHost; +using browser_sync::ThemeChangeProcessor; +using browser_sync::ThemeDataTypeController; +using browser_sync::ThemeModelAssociator; using browser_sync::TypedUrlChangeProcessor; using browser_sync::TypedUrlDataTypeController; using browser_sync::TypedUrlModelAssociator; @@ -75,6 +81,13 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService() { new PreferenceDataTypeController(this, pss)); } + // Theme sync is disabled by default. Register only if explicitly + // enabled. + if (command_line_->HasSwitch(switches::kEnableSyncThemes)) { + pss->RegisterDataTypeController( + new ThemeDataTypeController(this, profile_, pss)); + } + // TypedUrl sync is disabled by default. Register only if // explicitly enabled. if (command_line_->HasSwitch(switches::kEnableSyncTypedUrls)) { @@ -134,6 +147,18 @@ ProfileSyncFactoryImpl::CreatePreferenceSyncComponents( } ProfileSyncFactory::SyncComponents +ProfileSyncFactoryImpl::CreateThemeSyncComponents( + ProfileSyncService* profile_sync_service, + UnrecoverableErrorHandler* error_handler) { + ThemeModelAssociator* model_associator = + new ThemeModelAssociator(profile_sync_service, + error_handler); + ThemeChangeProcessor* change_processor = + new ThemeChangeProcessor(error_handler); + return SyncComponents(model_associator, change_processor); +} + +ProfileSyncFactory::SyncComponents ProfileSyncFactoryImpl::CreateTypedUrlSyncComponents( ProfileSyncService* profile_sync_service, history::HistoryBackend* history_backend, diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h index effec7e..8b1cb32 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.h +++ b/chrome/browser/sync/profile_sync_factory_impl.h @@ -36,6 +36,10 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory { ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreateThemeSyncComponents( + ProfileSyncService* profile_sync_service, + browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreateTypedUrlSyncComponents( ProfileSyncService* profile_sync_service, history::HistoryBackend* history_backend, diff --git a/chrome/browser/sync/profile_sync_factory_impl_unittest.cc b/chrome/browser/sync/profile_sync_factory_impl_unittest.cc index ce901f2..fb400ce 100644 --- a/chrome/browser/sync/profile_sync_factory_impl_unittest.cc +++ b/chrome/browser/sync/profile_sync_factory_impl_unittest.cc @@ -73,3 +73,13 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSEnablePreferences) { EXPECT_EQ(1U, controllers.count(syncable::BOOKMARKS)); EXPECT_EQ(1U, controllers.count(syncable::PREFERENCES)); } + +TEST_F(ProfileSyncFactoryImplTest, CreatePSSEnableThemes) { + command_line_->AppendSwitch(switches::kEnableSyncThemes); + scoped_ptr<ProfileSyncService> pss; + pss.reset(profile_sync_service_factory_->CreateProfileSyncService()); + DataTypeController::TypeMap controllers(pss->data_type_controllers()); + EXPECT_EQ(2U, controllers.size()); + EXPECT_EQ(1U, controllers.count(syncable::BOOKMARKS)); + EXPECT_EQ(1U, controllers.count(syncable::THEMES)); +} diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index ba4d078..806899e 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_mock.h @@ -39,6 +39,9 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory { MOCK_METHOD2(CreatePreferenceSyncComponents, SyncComponents(ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler)); + MOCK_METHOD2(CreateThemeSyncComponents, + SyncComponents(ProfileSyncService* profile_sync_service, + browser_sync::UnrecoverableErrorHandler* error_handler)); MOCK_METHOD3(CreateTypedUrlSyncComponents, SyncComponents( ProfileSyncService* profile_sync_service, diff --git a/chrome/browser/sync/protocol/theme_specifics.proto b/chrome/browser/sync/protocol/theme_specifics.proto new file mode 100644 index 0000000..89af3a0 --- /dev/null +++ b/chrome/browser/sync/protocol/theme_specifics.proto @@ -0,0 +1,40 @@ +// 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. +// +// Sync protocol datatype extension for themes. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package sync_pb; + +import "sync.proto"; + +// Properties of theme sync objects. +message ThemeSpecifics { + // If set, we're using a custom theme and all custom_* fields should be + // present. If not set, we use the default or system theme (see below) + // and all custom_* fields should be omitted. + optional bool use_custom_theme = 1; + // This field is only set (i.e., not cleared) on platforms that have + // a distinction between the system theme and the default theme, but + // other platforms must be careful to pass through the set state (not + // just the value) of this flag. + // + // If true, we use the system theme by default (i.e., when we don't use + // a custom theme) for platforms that make a distinction between the + // default theme and the system theme. Has no effect if use_custom_theme + // is set. + optional bool use_system_theme_by_default = 2; + + // Custom-theme-specific fields. + optional string custom_theme_name = 3; + optional string custom_theme_id = 4; + optional string custom_theme_update_url = 5; +} + +extend EntitySpecifics { + optional ThemeSpecifics theme = 41210; +} diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc index 63a8d65..1e66917 100644 --- a/chrome/browser/sync/syncable/model_type.cc +++ b/chrome/browser/sync/syncable/model_type.cc @@ -8,8 +8,9 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" -#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/protocol/sync.pb.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" namespace syncable { @@ -25,6 +26,9 @@ void AddDefaultExtensionValue(syncable::ModelType datatype, case AUTOFILL: specifics->MutableExtension(sync_pb::autofill); break; + case THEMES: + specifics->MutableExtension(sync_pb::theme); + break; case TYPED_URLS: specifics->MutableExtension(sync_pb::typed_url); break; @@ -53,6 +57,9 @@ ModelType GetModelType(const sync_pb::SyncEntity& sync_pb_entity) { if (sync_entity.specifics().HasExtension(sync_pb::autofill)) return syncable::AUTOFILL; + if (sync_entity.specifics().HasExtension(sync_pb::theme)) + return syncable::THEMES; + if (sync_entity.specifics().HasExtension(sync_pb::typed_url)) return syncable::TYPED_URLS; diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h index 7fc5655..3658834 100644 --- a/chrome/browser/sync/syncable/model_type.h +++ b/chrome/browser/sync/syncable/model_type.h @@ -28,6 +28,7 @@ enum ModelType { BOOKMARKS, // A bookmark folder or a bookmark URL object. PREFERENCES, // A preference folder or a preference object. AUTOFILL, // An autofill folder or an autofill object. + THEMES, // A themes folder or a themes object. TYPED_URLS, // A typed_url folder or a typed_url object. MODEL_TYPE_COUNT, }; diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc index 08a7bf5..87479a2 100644 --- a/chrome/browser/sync/syncable/syncable.cc +++ b/chrome/browser/sync/syncable/syncable.cc @@ -39,6 +39,7 @@ #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" #include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/protocol/theme_specifics.pb.h" #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/syncable/directory_backing_store.h" #include "chrome/browser/sync/syncable/directory_manager.h" @@ -1058,6 +1059,8 @@ syncable::ModelType Entry::GetServerModelType() const { return PREFERENCES; if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::autofill)) return AUTOFILL; + if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::theme)) + return THEMES; if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::typed_url)) return TYPED_URLS; if (IsRoot()) @@ -1087,6 +1090,8 @@ syncable::ModelType Entry::GetModelType() const { return PREFERENCES; if (Get(SPECIFICS).HasExtension(sync_pb::autofill)) return AUTOFILL; + if (Get(SPECIFICS).HasExtension(sync_pb::theme)) + return THEMES; if (Get(SPECIFICS).HasExtension(sync_pb::typed_url)) return TYPED_URLS; if (IsRoot()) |