summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync
diff options
context:
space:
mode:
authorakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-29 20:04:05 +0000
committerakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-29 20:04:05 +0000
commit60cab460a9f891d34efacddd3efc10d89279b6d6 (patch)
tree5edbf5cafb368c09a386bf3dcf122077d067c396 /chrome/browser/sync
parentc5e401ad5812699fafaf41492015eb26174084ed (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/sync/engine/syncapi.cc19
-rw-r--r--chrome/browser/sync/engine/syncapi.h11
-rw-r--r--chrome/browser/sync/glue/data_type_manager_impl.cc1
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h1
-rw-r--r--chrome/browser/sync/glue/theme_change_processor.cc198
-rw-r--r--chrome/browser/sync/glue/theme_change_processor.h70
-rw-r--r--chrome/browser/sync/glue/theme_data_type_controller.cc120
-rw-r--r--chrome/browser/sync/glue/theme_data_type_controller.h81
-rw-r--r--chrome/browser/sync/glue/theme_data_type_controller_unittest.cc172
-rw-r--r--chrome/browser/sync/glue/theme_model_associator.cc111
-rw-r--r--chrome/browser/sync/glue/theme_model_associator.h44
-rw-r--r--chrome/browser/sync/glue/theme_util.cc190
-rw-r--r--chrome/browser/sync/glue/theme_util.h55
-rw-r--r--chrome/browser/sync/glue/theme_util_unittest.cc218
-rw-r--r--chrome/browser/sync/profile_sync_factory.h7
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.cc25
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.h4
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl_unittest.cc10
-rw-r--r--chrome/browser/sync/profile_sync_factory_mock.h3
-rw-r--r--chrome/browser/sync/protocol/theme_specifics.proto40
-rw-r--r--chrome/browser/sync/syncable/model_type.cc9
-rw-r--r--chrome/browser/sync/syncable/model_type.h1
-rw-r--r--chrome/browser/sync/syncable/syncable.cc5
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())