summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-14 19:59:15 +0000
committerakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-14 19:59:15 +0000
commitc05d2cb72ed6f2dcbe6d359665841a0d1b481390 (patch)
treef2f71c08c0f66b7ae8e118eef83e7556a4c2d429
parent4f3d77cd54c36802154cb8c483bed0abdcf75b8d (diff)
downloadchromium_src-c05d2cb72ed6f2dcbe6d359665841a0d1b481390.zip
chromium_src-c05d2cb72ed6f2dcbe6d359665841a0d1b481390.tar.gz
chromium_src-c05d2cb72ed6f2dcbe6d359665841a0d1b481390.tar.bz2
Implemented initial version of extension syncing.
BUG=32413 TEST=unit tests, trybots, manual Review URL: http://codereview.chromium.org/2752007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49712 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/sync/engine/syncapi.cc27
-rw-r--r--chrome/browser/sync/engine/syncapi.h9
-rw-r--r--chrome/browser/sync/glue/extension_change_processor.cc165
-rw-r--r--chrome/browser/sync/glue/extension_change_processor.h72
-rw-r--r--chrome/browser/sync/glue/extension_data.cc50
-rw-r--r--chrome/browser/sync/glue/extension_data.h62
-rw-r--r--chrome/browser/sync/glue/extension_data_type_controller.cc111
-rw-r--r--chrome/browser/sync/glue/extension_data_type_controller.h84
-rw-r--r--chrome/browser/sync/glue/extension_data_type_controller_unittest.cc175
-rw-r--r--chrome/browser/sync/glue/extension_data_unittest.cc93
-rw-r--r--chrome/browser/sync/glue/extension_model_associator.cc420
-rw-r--r--chrome/browser/sync/glue/extension_model_associator.h110
-rw-r--r--chrome/browser/sync/glue/extension_util.cc232
-rw-r--r--chrome/browser/sync/glue/extension_util.h112
-rw-r--r--chrome/browser/sync/glue/extension_util_unittest.cc420
-rw-r--r--chrome/browser/sync/profile_sync_factory.h7
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.cc24
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.h4
-rw-r--r--chrome/browser/sync/profile_sync_factory_mock.h3
-rw-r--r--chrome/browser/sync/syncable/model_type.cc2
-rwxr-xr-xchrome/chrome_browser.gypi10
-rwxr-xr-xchrome/chrome_tests.gypi3
22 files changed, 2188 insertions, 7 deletions
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc
index 7018d74..1a2da8b 100644
--- a/chrome/browser/sync/engine/syncapi.cc
+++ b/chrome/browser/sync/engine/syncapi.cc
@@ -1613,13 +1613,26 @@ void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id,
const syncable::EntryKernel& original, bool existed_before,
bool exists_now) {
// Extra data for autofill deletions.
- if (type == syncable::AUTOFILL) {
- if (!exists_now && existed_before) {
- sync_pb::AutofillSpecifics* s = new sync_pb::AutofillSpecifics;
- s->CopyFrom(original.ref(SPECIFICS).GetExtension(sync_pb::autofill));
- ExtraChangeRecordData* extra = new ExtraAutofillChangeRecordData(s);
- buffer->SetExtraDataForId(id, extra);
- }
+ switch (type) {
+ case syncable::AUTOFILL:
+ if (!exists_now && existed_before) {
+ sync_pb::AutofillSpecifics* s = new sync_pb::AutofillSpecifics;
+ s->CopyFrom(original.ref(SPECIFICS).GetExtension(sync_pb::autofill));
+ ExtraChangeRecordData* extra = new ExtraAutofillChangeRecordData(s);
+ buffer->SetExtraDataForId(id, extra);
+ }
+ break;
+ case syncable::EXTENSIONS:
+ if (!exists_now && existed_before) {
+ const std::string& extension_id =
+ original.ref(SPECIFICS).GetExtension(sync_pb::extension).id();
+ ExtraChangeRecordData* extra =
+ new ExtraExtensionChangeRecordData(extension_id);
+ buffer->SetExtraDataForId(id, extra);
+ }
+ break;
+ default:
+ break;
}
}
diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h
index b363188..71ca150 100644
--- a/chrome/browser/sync/engine/syncapi.h
+++ b/chrome/browser/sync/engine/syncapi.h
@@ -529,6 +529,15 @@ class SyncManager {
const sync_pb::AutofillSpecifics* pre_deletion_data;
};
+ // Extra data used only for extension DELETE changes.
+ class ExtraExtensionChangeRecordData : public ExtraChangeRecordData {
+ public:
+ explicit ExtraExtensionChangeRecordData(const std::string& extension_id)
+ : extension_id(extension_id) {}
+ virtual ~ExtraExtensionChangeRecordData() {}
+ const std::string extension_id;
+ };
+
// Status encapsulates detailed state about the internals of the SyncManager.
struct Status {
// Summary is a distilled set of important information that the end-user may
diff --git a/chrome/browser/sync/glue/extension_change_processor.cc b/chrome/browser/sync/glue/extension_change_processor.cc
new file mode 100644
index 0000000..36a7dc1
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_change_processor.cc
@@ -0,0 +1,165 @@
+// 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/extension_change_processor.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/logging.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/glue/extension_model_associator.h"
+#include "chrome/browser/sync/glue/extension_util.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_source.h"
+
+typedef sync_api::SyncManager::ExtraExtensionChangeRecordData
+ ExtraExtensionChangeRecordData;
+
+namespace browser_sync {
+
+ExtensionChangeProcessor::ExtensionChangeProcessor(
+ UnrecoverableErrorHandler* error_handler,
+ ExtensionModelAssociator* extension_model_associator)
+ : ChangeProcessor(error_handler),
+ extension_model_associator_(extension_model_associator),
+ profile_(NULL) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(error_handler);
+ DCHECK(extension_model_associator_);
+}
+
+ExtensionChangeProcessor::~ExtensionChangeProcessor() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+}
+
+// TODO(akalin): We need to make sure events we receive from either
+// the browser or the syncapi are done in order; this is tricky since
+// some events (e.g., extension installation) are done asynchronously.
+
+void ExtensionChangeProcessor::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(running());
+ DCHECK(profile_);
+ switch (type.value) {
+ case NotificationType::EXTENSION_LOADED:
+ case NotificationType::EXTENSION_UNLOADED: {
+ DCHECK_EQ(Source<Profile>(source).ptr(), profile_);
+ Extension* extension = Details<Extension>(details).ptr();
+ CHECK(extension);
+ const std::string& id = extension->id();
+ LOG(INFO) << "Got change notification of type " << type.value
+ << " for extension " << id;
+ if (!extension_model_associator_->OnClientUpdate(id)) {
+ std::string error = std::string("Client update failed for ") + id;
+ error_handler()->OnUnrecoverableError(FROM_HERE, error);
+ return;
+ }
+ break;
+ }
+ default:
+ LOG(DFATAL) << "Received unexpected notification of type "
+ << type.value;
+ break;
+ }
+
+ return;
+}
+
+void ExtensionChangeProcessor::ApplyChangesFromSyncModel(
+ const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord* changes,
+ int change_count) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ if (!running()) {
+ return;
+ }
+ for (int i = 0; i < change_count; ++i) {
+ const sync_api::SyncManager::ChangeRecord& change = changes[i];
+ switch (change.action) {
+ case sync_api::SyncManager::ChangeRecord::ACTION_ADD:
+ case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: {
+ sync_api::ReadNode node(trans);
+ if (!node.InitByIdLookup(change.id)) {
+ std::stringstream error;
+ error << "Extension node lookup failed for change " << change.id
+ << " of action type " << change.action;
+ error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
+ return;
+ }
+ DCHECK_EQ(node.GetModelType(), syncable::EXTENSIONS);
+ const sync_pb::ExtensionSpecifics& specifics =
+ node.GetExtensionSpecifics();
+ if (!IsExtensionSpecificsValid(specifics)) {
+ std::string error =
+ std::string("Invalid server specifics: ") +
+ ExtensionSpecificsToString(specifics);
+ error_handler()->OnUnrecoverableError(FROM_HERE, error);
+ return;
+ }
+ StopObserving();
+ extension_model_associator_->OnServerUpdate(specifics);
+ StartObserving();
+ break;
+ }
+ case sync_api::SyncManager::ChangeRecord::ACTION_DELETE: {
+ StopObserving();
+ scoped_ptr<ExtraExtensionChangeRecordData>
+ data(static_cast<ExtraExtensionChangeRecordData*>(change.extra));
+ if (data.get()) {
+ extension_model_associator_->OnServerRemove(data->extension_id);
+ } else {
+ std::stringstream error;
+ error << "Could not get extension ID for deleted node "
+ << change.id;
+ error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
+ LOG(DFATAL) << error.str();
+ }
+ StartObserving();
+ break;
+ }
+ }
+ }
+}
+
+void ExtensionChangeProcessor::StartImpl(Profile* profile) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(profile);
+ profile_ = profile;
+ StartObserving();
+}
+
+void ExtensionChangeProcessor::StopImpl() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ StopObserving();
+ profile_ = NULL;
+}
+
+void ExtensionChangeProcessor::StartObserving() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(profile_);
+ LOG(INFO) << "Observing EXTENSION_LOADED and EXTENSION_UNLOADED";
+ // TODO(akalin): We miss notifications when we uninstall a disabled
+ // extension since EXTENSION_UNLOADED isn't sent in that case. Add
+ // an EXTENSION_UNINSTALLED notification and listen to it.
+ notification_registrar_.Add(
+ this, NotificationType::EXTENSION_LOADED,
+ Source<Profile>(profile_));
+ notification_registrar_.Add(
+ this, NotificationType::EXTENSION_UNLOADED,
+ Source<Profile>(profile_));
+}
+
+void ExtensionChangeProcessor::StopObserving() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(profile_);
+ LOG(INFO) << "Unobserving all notifications";
+ notification_registrar_.RemoveAll();
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/extension_change_processor.h b/chrome/browser/sync/glue/extension_change_processor.h
new file mode 100644
index 0000000..7a10f9d
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_change_processor.h
@@ -0,0 +1,72 @@
+// 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_EXTENSION_CHANGE_PROCESSOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_EXTENSION_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 ExtensionModelAssociator;
+class UnrecoverableErrorHandler;
+
+// This class is responsible for taking changes from the
+// ExtensionService 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 ExtensionChangeProcessor : public ChangeProcessor,
+ public NotificationObserver {
+ public:
+ // Does not take ownership of either argument.
+ //
+ // TODO(akalin): Create a Delegate interface and take that instead.
+ // That'll enable us to unit test this class.
+ ExtensionChangeProcessor(
+ UnrecoverableErrorHandler* error_handler,
+ ExtensionModelAssociator* extension_model_associator);
+ virtual ~ExtensionChangeProcessor();
+
+ // NotificationObserver implementation.
+ // BrowserExtensionProvider -> sync_api model change application.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // ChangeProcessor implementation.
+ // sync_api model -> BrowserExtensionProvider 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 StartObserving();
+ void StopObserving();
+
+ ExtensionModelAssociator* extension_model_associator_;
+ NotificationRegistrar notification_registrar_;
+ // Owner of the ExtensionService. Non-NULL iff |running()| is true.
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionChangeProcessor);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_CHANGE_PROCESSOR_H_
diff --git a/chrome/browser/sync/glue/extension_data.cc b/chrome/browser/sync/glue/extension_data.cc
new file mode 100644
index 0000000..eb4fea6
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_data.cc
@@ -0,0 +1,50 @@
+// 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/extension_data.h"
+
+#include "base/logging.h"
+#include "chrome/browser/sync/glue/extension_util.h"
+
+namespace browser_sync {
+
+ExtensionData ExtensionData::FromData(
+ Source source, const sync_pb::ExtensionSpecifics& data) {
+ DcheckIsExtensionSpecificsValid(data);
+ ExtensionData extension_data;
+ extension_data.merged_data_ = extension_data.source_data_[source] = data;
+ DCHECK(AreExtensionSpecificsEqual(extension_data.merged_data(), data));
+ DCHECK(!extension_data.NeedsUpdate(source));
+ return extension_data;
+}
+
+const sync_pb::ExtensionSpecifics& ExtensionData::merged_data() const {
+ DcheckIsExtensionSpecificsValid(merged_data_);
+ return merged_data_;
+}
+
+bool ExtensionData::NeedsUpdate(Source source) const {
+ SourceDataMap::const_iterator it = source_data_.find(source);
+ return
+ (it == source_data_.end()) ||
+ !AreExtensionSpecificsEqual(it->second, merged_data_);
+}
+
+void ExtensionData::SetData(
+ Source source, bool merge_user_properties,
+ const sync_pb::ExtensionSpecifics& data) {
+ DcheckIsExtensionSpecificsValid(data);
+ source_data_[source] = data;
+ MergeExtensionSpecifics(data, merge_user_properties, &merged_data_);
+ DcheckIsExtensionSpecificsValid(merged_data_);
+}
+
+void ExtensionData::ResolveData(Source source) {
+ source_data_[source] = merged_data_;
+ DCHECK(!NeedsUpdate(source));
+}
+
+ExtensionData::ExtensionData() {}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/extension_data.h b/chrome/browser/sync/glue/extension_data.h
new file mode 100644
index 0000000..f67a48f
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_data.h
@@ -0,0 +1,62 @@
+// 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_EXTENSION_DATA_H_
+#define CHROME_BROWSER_SYNC_GLUE_EXTENSION_DATA_H_
+
+// ExtensionData is the class used to manage the client and server
+// versions of the data for a particular extension.
+
+#include <map>
+
+#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
+
+namespace browser_sync {
+
+class ExtensionData {
+ public:
+ enum Source {
+ CLIENT,
+ SERVER,
+ };
+
+ // Returns an ExtensionData constructed from the given data from the
+ // given source. merged_data() will be equal to |data| and
+ // NeedsUpdate(source) will return false.
+ static ExtensionData FromData(
+ Source source, const sync_pb::ExtensionSpecifics& data);
+
+ // Implicit copy constructor and assignment operator welcome.
+
+ // Returns the version of the data that all sources should
+ // eventually have.
+ const sync_pb::ExtensionSpecifics& merged_data() const;
+
+ // Returns whether or not the data from the given source needs to be
+ // updated from merged_data().
+ bool NeedsUpdate(Source source) const;
+
+ // Stores the given data as being from the given source, merging it
+ // into merged_data(). May change what NeedsUpdate() returns for
+ // any source.
+ void SetData(
+ Source source, bool merge_user_properties,
+ const sync_pb::ExtensionSpecifics& data);
+
+ // Marks the data from the given data as updated,
+ // i.e. NeedsUpdate(source) will return false.
+ void ResolveData(Source source);
+
+ private:
+ typedef std::map<Source, sync_pb::ExtensionSpecifics> SourceDataMap;
+ SourceDataMap source_data_;
+ sync_pb::ExtensionSpecifics merged_data_;
+
+ // This is private; clients must use FromData().
+ ExtensionData();
+};
+
+} // namespace
+
+#endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_DATA_H_
diff --git a/chrome/browser/sync/glue/extension_data_type_controller.cc b/chrome/browser/sync/glue/extension_data_type_controller.cc
new file mode 100644
index 0000000..c71ce3c
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_data_type_controller.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 "base/histogram.h"
+#include "base/logging.h"
+#include "base/time.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/glue/extension_change_processor.h"
+#include "chrome/browser/sync/glue/extension_data_type_controller.h"
+#include "chrome/browser/sync/glue/extension_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 {
+
+ExtensionDataTypeController::ExtensionDataTypeController(
+ ProfileSyncFactory* profile_sync_factory,
+ Profile* profile,
+ ProfileSyncService* sync_service)
+ : profile_sync_factory_(profile_sync_factory),
+ profile_(profile),
+ sync_service_(sync_service),
+ state_(NOT_RUNNING) {
+ DCHECK(profile_sync_factory);
+ DCHECK(sync_service);
+}
+
+ExtensionDataTypeController::~ExtensionDataTypeController() {
+}
+
+void ExtensionDataTypeController::Start(StartCallback* start_callback) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(start_callback);
+ 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_->CreateExtensionSyncComponents(sync_service_,
+ this);
+ model_associator_.reset(sync_components.model_associator);
+ change_processor_.reset(sync_components.change_processor);
+
+ bool sync_has_nodes = false;
+ if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) {
+ StartFailed(UNRECOVERABLE_ERROR);
+ return;
+ }
+
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ bool merge_success = model_associator_->AssociateModels();
+ UMA_HISTOGRAM_TIMES("Sync.ExtensionAssociationTime",
+ 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 ExtensionDataTypeController::Stop() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ if (change_processor_ != NULL)
+ sync_service_->DeactivateDataType(this, change_processor_.get());
+
+ if (model_associator_ != NULL)
+ model_associator_->DisassociateModels();
+
+ change_processor_.reset();
+ model_associator_.reset();
+ start_callback_.reset();
+
+ state_ = NOT_RUNNING;
+}
+
+void ExtensionDataTypeController::OnUnrecoverableError(
+ const tracked_objects::Location& from_here,
+ const std::string& message) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ UMA_HISTOGRAM_COUNTS("Sync.ExtensionRunFailures", 1);
+ sync_service_->OnUnrecoverableError(from_here, message);
+}
+
+void ExtensionDataTypeController::FinishStart(StartResult result) {
+ start_callback_->Run(result);
+ start_callback_.reset();
+}
+
+void ExtensionDataTypeController::StartFailed(StartResult result) {
+ model_associator_.reset();
+ change_processor_.reset();
+ start_callback_->Run(result);
+ start_callback_.reset();
+ UMA_HISTOGRAM_ENUMERATION("Sync.ExtensionStartFailures",
+ result,
+ MAX_START_RESULT);
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/extension_data_type_controller.h b/chrome/browser/sync/glue/extension_data_type_controller.h
new file mode 100644
index 0000000..c364ac2
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_data_type_controller.h
@@ -0,0 +1,84 @@
+// 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_EXTENSION_DATA_TYPE_CONTROLLER_H_
+#define CHROME_BROWSER_SYNC_GLUE_EXTENSION_DATA_TYPE_CONTROLLER_H_
+
+#include <string>
+
+#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 ExtensionDataTypeController : public DataTypeController {
+ public:
+ ExtensionDataTypeController(
+ ProfileSyncFactory* profile_sync_factory,
+ Profile* profile,
+ ProfileSyncService* sync_service);
+ virtual ~ExtensionDataTypeController();
+
+ // DataTypeController implementation.
+ virtual void Start(StartCallback* start_callback);
+
+ virtual void Stop();
+
+ virtual bool enabled() {
+ return true;
+ }
+
+ virtual syncable::ModelType type() {
+ return syncable::EXTENSIONS;
+ }
+
+ virtual browser_sync::ModelSafeGroup model_safe_group() {
+ return browser_sync::GROUP_UI;
+ }
+
+ virtual const char* name() const {
+ // For logging only.
+ return "extension";
+ }
+
+ virtual State state() {
+ return state_;
+ }
+
+ // UnrecoverableErrorHandler interface.
+ virtual void OnUnrecoverableError(
+ const tracked_objects::Location& from_here,
+ const std::string& message);
+
+ 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_;
+
+ scoped_ptr<StartCallback> start_callback_;
+ scoped_ptr<AssociatorInterface> model_associator_;
+ scoped_ptr<ChangeProcessor> change_processor_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionDataTypeController);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_DATA_TYPE_CONTROLLER_H_
diff --git a/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc b/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc
new file mode 100644
index 0000000..a8d4979
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc
@@ -0,0 +1,175 @@
+// 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/extension_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::ExtensionDataTypeController;
+using browser_sync::ChangeProcessorMock;
+using browser_sync::DataTypeController;
+using browser_sync::ModelAssociatorMock;
+using testing::_;
+using testing::DoAll;
+using testing::InvokeWithoutArgs;
+using testing::Return;
+using testing::SetArgumentPointee;
+
+class StartCallback {
+ public:
+ MOCK_METHOD1(Run, void(DataTypeController::StartResult result));
+};
+
+class ExtensionDataTypeControllerTest : public testing::Test {
+ public:
+ ExtensionDataTypeControllerTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_) {}
+
+ virtual void SetUp() {
+ profile_sync_factory_.reset(new ProfileSyncFactoryMock());
+ extension_dtc_ =
+ new ExtensionDataTypeController(profile_sync_factory_.get(),
+ &profile_, &service_);
+ }
+
+ protected:
+ void SetStartExpectations() {
+ model_associator_ = new ModelAssociatorMock();
+ change_processor_ = new ChangeProcessorMock();
+ EXPECT_CALL(*profile_sync_factory_, CreateExtensionSyncComponents(_, _)).
+ 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<ExtensionDataTypeController> extension_dtc_;
+ scoped_ptr<ProfileSyncFactoryMock> profile_sync_factory_;
+ ProfileMock profile_;
+ ProfileSyncServiceMock service_;
+ ModelAssociatorMock* model_associator_;
+ ChangeProcessorMock* change_processor_;
+ StartCallback start_callback_;
+};
+
+TEST_F(ExtensionDataTypeControllerTest, Start) {
+ SetStartExpectations();
+ SetAssociateExpectations();
+ SetActivateExpectations();
+ EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state());
+ EXPECT_CALL(start_callback_, Run(DataTypeController::OK));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+ EXPECT_EQ(DataTypeController::RUNNING, extension_dtc_->state());
+}
+
+TEST_F(ExtensionDataTypeControllerTest, 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));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+}
+
+TEST_F(ExtensionDataTypeControllerTest, StartOk) {
+ 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));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+}
+
+TEST_F(ExtensionDataTypeControllerTest, StartAssociationFailed) {
+ SetStartExpectations();
+ SetAssociateExpectations();
+ EXPECT_CALL(*model_associator_, AssociateModels()).
+ WillRepeatedly(Return(false));
+
+ EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+ EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state());
+}
+
+TEST_F(ExtensionDataTypeControllerTest,
+ 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));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+ EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state());
+}
+
+TEST_F(ExtensionDataTypeControllerTest, Stop) {
+ SetStartExpectations();
+ SetAssociateExpectations();
+ SetActivateExpectations();
+ SetStopExpectations();
+
+ EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state());
+
+ EXPECT_CALL(start_callback_, Run(DataTypeController::OK));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+ EXPECT_EQ(DataTypeController::RUNNING, extension_dtc_->state());
+ extension_dtc_->Stop();
+ EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state());
+}
+
+// TODO(akalin): Add this test to all the other DTCs.
+TEST_F(ExtensionDataTypeControllerTest, OnUnrecoverableError) {
+ 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(service_, OnUnrecoverableError(_, _)).
+ WillOnce(InvokeWithoutArgs(extension_dtc_.get(),
+ &ExtensionDataTypeController::Stop));
+ SetStopExpectations();
+
+ EXPECT_CALL(start_callback_, Run(DataTypeController::OK));
+ extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run));
+ // This should cause extension_dtc_->Stop() to be called.
+ extension_dtc_->OnUnrecoverableError(FROM_HERE, "Test");
+}
diff --git a/chrome/browser/sync/glue/extension_data_unittest.cc b/chrome/browser/sync/glue/extension_data_unittest.cc
new file mode 100644
index 0000000..a5fdddd
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_data_unittest.cc
@@ -0,0 +1,93 @@
+// 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/extension_data.h"
+
+#include "chrome/browser/sync/glue/extension_util.h"
+#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser_sync {
+
+namespace {
+
+class ExtensionDataTest : public testing::Test {
+};
+
+const char kValidId[] = "abcdefghijklmnopabcdefghijklmnop";
+const char kValidUpdateUrl1[] = "http://www.google.com";
+const char kValidUpdateUrl2[] = "http://www.bing.com";
+const char kValidVersion1[] = "1.0.0.0";
+const char kValidVersion2[] = "1.1.0.0";
+
+TEST_F(ExtensionDataTest, FromData) {
+ sync_pb::ExtensionSpecifics client_data;
+ client_data.set_id(kValidId);
+ client_data.set_update_url(kValidUpdateUrl1);
+ client_data.set_version(kValidVersion1);
+ ExtensionData extension_data =
+ ExtensionData::FromData(ExtensionData::CLIENT, client_data);
+ EXPECT_FALSE(extension_data.NeedsUpdate(ExtensionData::CLIENT));
+ EXPECT_TRUE(extension_data.NeedsUpdate(ExtensionData::SERVER));
+ EXPECT_TRUE(AreExtensionSpecificsEqual(
+ client_data, extension_data.merged_data()));
+}
+
+TEST_F(ExtensionDataTest, SetData) {
+ sync_pb::ExtensionSpecifics client_data;
+ client_data.set_id(kValidId);
+ client_data.set_update_url(kValidUpdateUrl1);
+ client_data.set_version(kValidVersion1);
+ ExtensionData extension_data =
+ ExtensionData::FromData(ExtensionData::CLIENT, client_data);
+
+ {
+ sync_pb::ExtensionSpecifics server_data;
+ server_data.set_id(kValidId);
+ server_data.set_update_url(kValidUpdateUrl2);
+ server_data.set_version(kValidVersion2);
+ server_data.set_enabled(true);
+ extension_data.SetData(ExtensionData::SERVER, false, server_data);
+ EXPECT_TRUE(extension_data.NeedsUpdate(ExtensionData::CLIENT));
+ EXPECT_TRUE(extension_data.NeedsUpdate(ExtensionData::SERVER));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics server_data;
+ server_data.set_id(kValidId);
+ server_data.set_update_url(kValidUpdateUrl2);
+ server_data.set_version(kValidVersion2);
+ server_data.set_enabled(true);
+ extension_data.SetData(ExtensionData::SERVER, true, server_data);
+ EXPECT_TRUE(extension_data.NeedsUpdate(ExtensionData::CLIENT));
+ EXPECT_FALSE(extension_data.NeedsUpdate(ExtensionData::SERVER));
+ EXPECT_TRUE(AreExtensionSpecificsEqual(
+ server_data, extension_data.merged_data()));
+ }
+}
+
+TEST_F(ExtensionDataTest, ResolveData) {
+ sync_pb::ExtensionSpecifics client_data;
+ client_data.set_id(kValidId);
+ client_data.set_update_url(kValidUpdateUrl1);
+ client_data.set_version(kValidVersion1);
+ ExtensionData extension_data =
+ ExtensionData::FromData(ExtensionData::CLIENT, client_data);
+
+ sync_pb::ExtensionSpecifics server_data;
+ server_data.set_id(kValidId);
+ server_data.set_update_url(kValidUpdateUrl2);
+ server_data.set_version(kValidVersion2);
+ extension_data.SetData(ExtensionData::SERVER, true, server_data);
+
+ extension_data.ResolveData(ExtensionData::CLIENT);
+ EXPECT_FALSE(extension_data.NeedsUpdate(ExtensionData::CLIENT));
+ EXPECT_FALSE(extension_data.NeedsUpdate(ExtensionData::SERVER));
+ EXPECT_TRUE(AreExtensionSpecificsEqual(
+ server_data, extension_data.merged_data()));
+}
+
+} // namespace
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/extension_model_associator.cc b/chrome/browser/sync/glue/extension_model_associator.cc
new file mode 100644
index 0000000..e3facec
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_model_associator.cc
@@ -0,0 +1,420 @@
+// 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/extension_model_associator.h"
+
+#include <map>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/extensions/extension_updater.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/glue/extension_util.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
+#include "chrome/common/extensions/extension.h"
+
+namespace browser_sync {
+
+namespace {
+
+static const char kExtensionsTag[] = "google_chrome_extensions";
+
+static const char kNoExtensionsFolderError[] =
+ "Server did not create the top-level extensions node. We "
+ "might be running against an out-of-date server.";
+
+typedef std::map<std::string, ExtensionData> ExtensionDataMap;
+
+ExtensionData* SetOrCreateData(
+ ExtensionDataMap* extension_data_map,
+ ExtensionData::Source source,
+ bool merge_user_properties,
+ const sync_pb::ExtensionSpecifics& data) {
+ DcheckIsExtensionSpecificsValid(data);
+ const std::string& extension_id = data.id();
+ std::pair<ExtensionDataMap::iterator, bool> result =
+ extension_data_map->insert(
+ std::make_pair(extension_id,
+ ExtensionData::FromData(source, data)));
+ ExtensionData* extension_data = &result.first->second;
+ if (result.second) {
+ // The value was just inserted, so it shouldn't need an update
+ // from source.
+ DCHECK(!extension_data->NeedsUpdate(source));
+ } else {
+ extension_data->SetData(source, merge_user_properties, data);
+ }
+ return extension_data;
+}
+
+void GetSyncableExtensionsClientData(
+ const ExtensionList& extensions,
+ ExtensionsService* extensions_service,
+ ExtensionDataMap* extension_data_map) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ for (ExtensionList::const_iterator it = extensions.begin();
+ it != extensions.end(); ++it) {
+ CHECK(*it);
+ const Extension& extension = **it;
+ if (IsExtensionSyncable(extension)) {
+ sync_pb::ExtensionSpecifics client_specifics;
+ GetExtensionSpecifics(extension, extensions_service,
+ &client_specifics);
+ DcheckIsExtensionSpecificsValid(client_specifics);
+ const ExtensionData& extension_data =
+ *SetOrCreateData(extension_data_map,
+ ExtensionData::CLIENT, true, client_specifics);
+ DcheckIsExtensionSpecificsValid(extension_data.merged_data());
+ // Assumes this is called before any server data is read.
+ DCHECK(extension_data.NeedsUpdate(ExtensionData::SERVER));
+ DCHECK(!extension_data.NeedsUpdate(ExtensionData::CLIENT));
+ }
+ }
+}
+
+} // namespace
+
+ExtensionModelAssociator::ExtensionModelAssociator(
+ ProfileSyncService* sync_service) : sync_service_(sync_service) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(sync_service_);
+}
+
+ExtensionModelAssociator::~ExtensionModelAssociator() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+}
+
+bool ExtensionModelAssociator::AssociateModels() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ sync_api::WriteTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ if (!root.InitByTagLookup(kExtensionsTag)) {
+ LOG(ERROR) << kNoExtensionsFolderError;
+ return false;
+ }
+
+ ExtensionDataMap extension_data_map;
+
+ // Read client-side data. Do this first so server data takes
+ // precedence.
+ {
+ ExtensionsService* extensions_service = GetExtensionsService();
+
+ const ExtensionList* extensions = extensions_service->extensions();
+ CHECK(extensions);
+ GetSyncableExtensionsClientData(
+ *extensions, extensions_service, &extension_data_map);
+
+ const ExtensionList* disabled_extensions =
+ extensions_service->disabled_extensions();
+ CHECK(disabled_extensions);
+ GetSyncableExtensionsClientData(
+ *disabled_extensions, extensions_service, &extension_data_map);
+ }
+
+ // Read server-side data.
+ {
+ int64 id = root.GetFirstChildId();
+ while (id != sync_api::kInvalidId) {
+ sync_api::ReadNode sync_node(&trans);
+ if (!sync_node.InitByIdLookup(id)) {
+ LOG(ERROR) << "Failed to fetch sync node for id " << id;
+ return false;
+ }
+ const sync_pb::ExtensionSpecifics& server_data =
+ sync_node.GetExtensionSpecifics();
+ if (!IsExtensionSpecificsValid(server_data)) {
+ LOG(ERROR) << "Invalid extensions specifics for id " << id;
+ return false;
+ }
+ // Pass in false for merge_user_properties so client user
+ // settings always take precedence.
+ const ExtensionData& extension_data =
+ *SetOrCreateData(&extension_data_map,
+ ExtensionData::SERVER, false, server_data);
+ DcheckIsExtensionSpecificsValid(extension_data.merged_data());
+ id = sync_node.GetSuccessorId();
+ }
+ }
+
+ // Update server and client as necessary.
+ bool should_nudge_extension_updater = false;
+ for (ExtensionDataMap::iterator it = extension_data_map.begin();
+ it != extension_data_map.end(); ++it) {
+ ExtensionData* extension_data = &it->second;
+ // Update server first.
+ if (extension_data->NeedsUpdate(ExtensionData::SERVER)) {
+ if (!UpdateServer(extension_data, &trans, root)) {
+ LOG(ERROR) << "Could not update server data for extension "
+ << it->first;
+ return false;
+ }
+ }
+ DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
+ if (extension_data->NeedsUpdate(ExtensionData::CLIENT)) {
+ TryUpdateClient(extension_data);
+ if (extension_data->NeedsUpdate(ExtensionData::CLIENT)) {
+ should_nudge_extension_updater = true;
+ }
+ }
+ DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
+ }
+
+ if (should_nudge_extension_updater) {
+ NudgeExtensionUpdater();
+ }
+
+ return true;
+}
+
+bool ExtensionModelAssociator::DisassociateModels() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ // Nothing to do.
+ return true;
+}
+
+bool ExtensionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ CHECK(has_nodes);
+ *has_nodes = false;
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ if (!root.InitByTagLookup(kExtensionsTag)) {
+ LOG(ERROR) << kNoExtensionsFolderError;
+ return false;
+ }
+ // The sync model has user created nodes iff the extensions folder has
+ // any children.
+ *has_nodes = root.GetFirstChildId() != sync_api::kInvalidId;
+ return true;
+}
+
+bool ExtensionModelAssociator::ChromeModelHasUserCreatedNodes(
+ bool* has_nodes) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ CHECK(has_nodes);
+ // This is wrong, but this function is unused, anyway.
+ *has_nodes = true;
+ return true;
+}
+
+bool ExtensionModelAssociator::OnClientUpdate(const std::string& id) {
+ sync_api::WriteTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ if (!root.InitByTagLookup(kExtensionsTag)) {
+ LOG(ERROR) << kNoExtensionsFolderError;
+ return false;
+ }
+
+ sync_pb::ExtensionSpecifics client_data;
+ if (GetExtensionDataFromClient(id, &client_data)) {
+ sync_pb::ExtensionSpecifics server_data;
+ if (!GetExtensionDataFromServer(id, &trans, root, &server_data)) {
+ LOG(ERROR) << "Could not get server data for extension " << id;
+ return false;
+ }
+ ExtensionData extension_data =
+ ExtensionData::FromData(ExtensionData::SERVER, server_data);
+ extension_data.SetData(ExtensionData::CLIENT, true, client_data);
+ if (extension_data.NeedsUpdate(ExtensionData::SERVER)) {
+ if (!UpdateServer(&extension_data, &trans, root)) {
+ LOG(ERROR) << "Could not update server data for extension " << id;
+ return false;
+ }
+ }
+ DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
+ // Client may still need updating, e.g. if we disable an extension
+ // while it's being auto-updated. If so, then we'll be called
+ // again once the auto-update is finished.
+ //
+ // TODO(akalin): Figure out a way to tell when the above happens,
+ // so we know exactly what NeedsUpdate(CLIENT) should return.
+ } else {
+ sync_api::WriteNode write_node(&trans);
+ if (write_node.InitByClientTagLookup(syncable::EXTENSIONS, id)) {
+ write_node.Remove();
+ } else {
+ LOG(ERROR) << "Trying to remove server data for "
+ << "nonexistent extension " << id;
+ }
+ }
+ return true;
+}
+
+void ExtensionModelAssociator::OnServerUpdate(
+ const sync_pb::ExtensionSpecifics& server_data) {
+ DcheckIsExtensionSpecificsValid(server_data);
+ sync_pb::ExtensionSpecifics client_data;
+ if (!GetExtensionDataFromClient(server_data.id(), &client_data)) {
+ client_data = sync_pb::ExtensionSpecifics();
+ }
+ ExtensionData extension_data =
+ ExtensionData::FromData(ExtensionData::CLIENT, client_data);
+ extension_data.SetData(ExtensionData::SERVER, true, server_data);
+ DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
+ if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) {
+ TryUpdateClient(&extension_data);
+ if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) {
+ NudgeExtensionUpdater();
+ }
+ }
+ DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
+}
+
+void ExtensionModelAssociator::OnServerRemove(const std::string& id) {
+ ExtensionsService* extensions_service = GetExtensionsService();
+ Extension* extension = extensions_service->GetExtensionById(id, true);
+ if (extension) {
+ extensions_service->UninstallExtension(id, false);
+ } else {
+ LOG(ERROR) << "Trying to uninstall nonexistent extension " << id;
+ }
+}
+
+ExtensionsService* ExtensionModelAssociator::GetExtensionsService() {
+ CHECK(sync_service_);
+ Profile* profile = sync_service_->profile();
+ CHECK(profile);
+ ExtensionsService* extensions_service = profile->GetExtensionsService();
+ CHECK(extensions_service);
+ return extensions_service;
+}
+
+bool ExtensionModelAssociator::GetExtensionDataFromClient(
+ const std::string& id, sync_pb::ExtensionSpecifics* client_data) {
+ ExtensionsService* extensions_service = GetExtensionsService();
+ Extension* extension = extensions_service->GetExtensionById(id, true);
+ if (!extension) {
+ return false;
+ }
+ GetExtensionSpecifics(*extension, extensions_service, client_data);
+ DcheckIsExtensionSpecificsValid(*client_data);
+ return true;
+}
+
+bool ExtensionModelAssociator::GetExtensionDataFromServer(
+ const std::string& id, sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode& root,
+ sync_pb::ExtensionSpecifics* server_data) {
+ sync_api::ReadNode sync_node(trans);
+ if (!sync_node.InitByClientTagLookup(syncable::EXTENSIONS, id)) {
+ LOG(ERROR) << "Failed to fetch sync node for id " << id;
+ return false;
+ }
+ const sync_pb::ExtensionSpecifics& read_server_data =
+ sync_node.GetExtensionSpecifics();
+ if (!IsExtensionSpecificsValid(read_server_data)) {
+ LOG(ERROR) << "Invalid extensions specifics for id " << id;
+ return false;
+ }
+ *server_data = read_server_data;
+ return true;
+}
+
+namespace {
+
+void SetNodeData(const sync_pb::ExtensionSpecifics& specifics,
+ sync_api::WriteNode* node) {
+ node->SetTitle(UTF8ToWide(specifics.name()));
+ node->SetExtensionSpecifics(specifics);
+}
+
+} // namespace
+
+bool ExtensionModelAssociator::UpdateServer(
+ ExtensionData* extension_data,
+ sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode& root) {
+ DCHECK(extension_data->NeedsUpdate(ExtensionData::SERVER));
+ const sync_pb::ExtensionSpecifics& specifics =
+ extension_data->merged_data();
+ const std::string& id = specifics.id();
+ sync_api::WriteNode write_node(trans);
+ if (write_node.InitByClientTagLookup(syncable::EXTENSIONS, id)) {
+ SetNodeData(specifics, &write_node);
+ } else {
+ sync_api::WriteNode create_node(trans);
+ if (!create_node.InitUniqueByCreation(syncable::EXTENSIONS, root, id)) {
+ LOG(ERROR) << "Could not create node for extension " << id;
+ return false;
+ }
+ SetNodeData(specifics, &create_node);
+ }
+ bool old_client_needs_update =
+ extension_data->NeedsUpdate(ExtensionData::CLIENT);
+ extension_data->ResolveData(ExtensionData::SERVER);
+ DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
+ DCHECK_EQ(extension_data->NeedsUpdate(ExtensionData::CLIENT),
+ old_client_needs_update);
+ return true;
+}
+
+void ExtensionModelAssociator::TryUpdateClient(
+ ExtensionData* extension_data) {
+ DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
+ DCHECK(extension_data->NeedsUpdate(ExtensionData::CLIENT));
+ const sync_pb::ExtensionSpecifics& specifics =
+ extension_data->merged_data();
+ DcheckIsExtensionSpecificsValid(specifics);
+ ExtensionsService* extensions_service = GetExtensionsService();
+ const std::string& id = specifics.id();
+ Extension* extension = extensions_service->GetExtensionById(id, true);
+ if (extension) {
+ SetExtensionProperties(specifics, extensions_service, extension);
+ {
+ sync_pb::ExtensionSpecifics extension_specifics;
+ GetExtensionSpecifics(*extension, extensions_service,
+ &extension_specifics);
+ DCHECK(AreExtensionSpecificsUserPropertiesEqual(
+ specifics, extension_specifics))
+ << ExtensionSpecificsToString(specifics) << ", "
+ << ExtensionSpecificsToString(extension_specifics);
+ }
+ if (!IsExtensionOutdated(*extension, specifics)) {
+ extension_data->ResolveData(ExtensionData::CLIENT);
+ DCHECK(!extension_data->NeedsUpdate(ExtensionData::CLIENT));
+ }
+ } else {
+ GURL update_url(specifics.update_url());
+ // TODO(akalin): The version number should be used only to
+ // determine auto-updating permissions, not to send to the
+ // auto-update server.
+ scoped_ptr<Version> version(
+ Version::GetVersionFromString("0.0.0.0"));
+ CHECK(version.get());
+ // TODO(akalin): Replace silent update with a list of enabled
+ // permissions.
+ //
+ // TODO(akalin): Pass through the enabled/incognito_enabled state.
+ // We can't do it on OnClientUpdate() because we'd run into
+ // problems with triggering notifications while we're in a
+ // notification handler. The bug that this causes is that syncing
+ // a fresh client (i.e., no extensions) re-enables disabled
+ // extensions.
+ extensions_service->AddPendingExtension(
+ id, update_url, *version, false, true);
+ }
+ DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
+}
+
+void ExtensionModelAssociator::NudgeExtensionUpdater() {
+ ExtensionUpdater* extension_updater = GetExtensionsService()->updater();
+ // Auto-updates should now be on always (see the construction of the
+ // ExtensionsService in ProfileImpl::InitExtensions()).
+ if (extension_updater) {
+ extension_updater->CheckNow();
+ } else {
+ LOG(DFATAL) << "Extension updater unexpectedly NULL; "
+ << "auto-updates may be turned off";
+ }
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/extension_model_associator.h b/chrome/browser/sync/glue/extension_model_associator.h
new file mode 100644
index 0000000..98a16fb
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_model_associator.h
@@ -0,0 +1,110 @@
+// 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_EXTENSION_MODEL_ASSOCIATOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_EXTENSION_MODEL_ASSOCIATOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/sync/glue/extension_data.h"
+#include "chrome/browser/sync/glue/model_associator.h"
+#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/common/extensions/extension.h"
+
+class ExtensionsService;
+class Profile;
+class ProfileSyncService;
+
+namespace sync_api {
+class ReadNode;
+class WriteTransaction;
+} // namespace sync_api
+
+namespace sync_pb {
+class ExtensionSpecifics;
+} // namespace sync_pb
+
+namespace browser_sync {
+
+// Contains all logic for associating the Chrome extensions model and
+// the sync extensions model.
+class ExtensionModelAssociator : public AssociatorInterface {
+ public:
+ // Does not take ownership of sync_service.
+ explicit ExtensionModelAssociator(ProfileSyncService* sync_service);
+ virtual ~ExtensionModelAssociator();
+
+ // Used by profile_sync_test_util.h.
+ static syncable::ModelType model_type() { return syncable::EXTENSIONS; }
+
+ // AssociatorInterface implementation.
+ virtual bool AssociateModels();
+ virtual bool DisassociateModels();
+ virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes);
+ // TODO(akalin): Remove this unused function.
+ virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes);
+ virtual void AbortAssociation() {
+ // No implementation needed, this associator runs on the main
+ // thread.
+ }
+
+ // Used by ExtensionChangeProcessor.
+ //
+ // TODO(akalin): These functions can actually be moved to the
+ // ChangeProcessor after some refactoring.
+
+ // TODO(akalin): Return an error string instead of just a bool.
+ bool OnClientUpdate(const std::string& id);
+ void OnServerUpdate(const sync_pb::ExtensionSpecifics& server_data);
+ void OnServerRemove(const std::string& id);
+
+ private:
+ // Returns the extension service from |sync_service_|. Never
+ // returns NULL.
+ ExtensionsService* GetExtensionsService();
+
+ bool GetExtensionDataFromClient(
+ const std::string& id, sync_pb::ExtensionSpecifics* client_data);
+
+ bool GetExtensionDataFromServer(
+ const std::string& id, sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode& root,
+ sync_pb::ExtensionSpecifics* server_data);
+
+ // Updates the server data from the given extension data.
+ // extension_data->ServerNeedsUpdate() must hold before this
+ // function is called. Returns whether or not the update was
+ // successful. If the update was successful,
+ // extension_data->ServerNeedsUpdate() will be false after this
+ // function is called. This function leaves
+ // extension_data->ClientNeedsUpdate() unchanged.
+ bool UpdateServer(ExtensionData* extension_data,
+ sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode& root);
+
+ // Tries to update the client data from the given extension data.
+ // extension_data->ServerNeedsUpdate() must not hold and
+ // extension_data->ClientNeedsUpdate() must hold before this
+ // function is called. If the update was successful,
+ // extension_data->ClientNeedsUpdate() will be false after this
+ // function is called. Otherwise, the extension needs updating to a
+ // new version.
+ void TryUpdateClient(ExtensionData* extension_data);
+
+ // Kick off a run of the extension updater.
+ //
+ // TODO(akalin): Combine this with the similar function in
+ // theme_util.cc.
+ void NudgeExtensionUpdater();
+
+ // Weak pointer.
+ ProfileSyncService* sync_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionModelAssociator);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_MODEL_ASSOCIATOR_H_
diff --git a/chrome/browser/sync/glue/extension_util.cc b/chrome/browser/sync/glue/extension_util.cc
new file mode 100644
index 0000000..e2b7505
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_util.cc
@@ -0,0 +1,232 @@
+// 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/extension_util.h"
+
+#include <sstream>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/version.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
+#include "chrome/common/extensions/extension.h"
+#include "googleurl/src/gurl.h"
+
+namespace browser_sync {
+
+bool IsExtensionSyncable(const Extension& extension) {
+ if (extension.IsTheme()) {
+ return false;
+ }
+
+ // TODO(akalin): Add Extensions::IsApp().
+ // TODO(akalin): Figure out if we want to treat extensions and apps
+ // identically after all.
+ if (!extension.GetFullLaunchURL().is_empty()) {
+ // We have an app.
+ return false;
+ }
+
+ // TODO(akalin): Figure out if we need to allow some other types.
+ if (extension.location() != Extension::INTERNAL) {
+ // We have a non-standard location.
+ return false;
+ }
+
+ return true;
+}
+
+std::string ExtensionSpecificsToString(
+ const sync_pb::ExtensionSpecifics& specifics) {
+ std::stringstream ss;
+ ss << "{ ";
+ ss << "id: " << specifics.id() << ", ";
+ ss << "version: " << specifics.version() << ", ";
+ ss << "update_url: " << specifics.update_url() << ", ";
+ ss << "enabled: " << specifics.enabled() << ", ";
+ ss << "incognito_enabled: " << specifics.incognito_enabled() << ", ";
+ ss << "name: " << specifics.name();
+ ss << " }";
+ return ss.str();
+}
+
+bool IsExtensionSpecificsValid(
+ const sync_pb::ExtensionSpecifics& specifics) {
+ if (!Extension::IdIsValid(specifics.id())) {
+ return false;
+ }
+
+ scoped_ptr<Version> version(
+ Version::GetVersionFromString(specifics.version()));
+ if (!version.get()) {
+ return false;
+ }
+
+ // The update URL must be either empty or valid.
+ GURL update_url(specifics.update_url());
+ if (!update_url.is_empty() && !update_url.is_valid()) {
+ return false;
+ }
+
+ return true;
+}
+
+void DcheckIsExtensionSpecificsValid(
+ const sync_pb::ExtensionSpecifics& specifics) {
+ DCHECK(IsExtensionSpecificsValid(specifics))
+ << ExtensionSpecificsToString(specifics);
+}
+
+bool AreExtensionSpecificsEqual(const sync_pb::ExtensionSpecifics& a,
+ const sync_pb::ExtensionSpecifics& b) {
+ // TODO(akalin): Figure out if we have to worry about version/URL
+ // strings that are not identical but map to the same object.
+ return ((a.id() == b.id()) &&
+ (a.version() == b.version()) &&
+ (a.update_url() == b.update_url()) &&
+ (a.enabled() == b.enabled()) &&
+ (a.incognito_enabled() == b.incognito_enabled()) &&
+ (a.name() == b.name()));
+}
+
+bool IsExtensionSpecificsUnset(
+ const sync_pb::ExtensionSpecifics& specifics) {
+ return AreExtensionSpecificsEqual(specifics,
+ sync_pb::ExtensionSpecifics());
+}
+
+void CopyUserProperties(
+ const sync_pb::ExtensionSpecifics& specifics,
+ sync_pb::ExtensionSpecifics* dest_specifics) {
+ DCHECK(dest_specifics);
+ dest_specifics->set_enabled(specifics.enabled());
+ dest_specifics->set_incognito_enabled(specifics.incognito_enabled());
+}
+
+void CopyNonUserProperties(
+ const sync_pb::ExtensionSpecifics& specifics,
+ sync_pb::ExtensionSpecifics* dest_specifics) {
+ DCHECK(dest_specifics);
+ sync_pb::ExtensionSpecifics old_dest_specifics(*dest_specifics);
+ *dest_specifics = specifics;
+ CopyUserProperties(old_dest_specifics, dest_specifics);
+}
+
+bool AreExtensionSpecificsUserPropertiesEqual(
+ const sync_pb::ExtensionSpecifics& a,
+ const sync_pb::ExtensionSpecifics& b) {
+ sync_pb::ExtensionSpecifics a_user_properties, b_user_properties;
+ CopyUserProperties(a, &a_user_properties);
+ CopyUserProperties(b, &b_user_properties);
+ return AreExtensionSpecificsEqual(a_user_properties, b_user_properties);
+}
+
+bool AreExtensionSpecificsNonUserPropertiesEqual(
+ const sync_pb::ExtensionSpecifics& a,
+ const sync_pb::ExtensionSpecifics& b) {
+ sync_pb::ExtensionSpecifics a_non_user_properties, b_non_user_properties;
+ CopyNonUserProperties(a, &a_non_user_properties);
+ CopyNonUserProperties(b, &b_non_user_properties);
+ return AreExtensionSpecificsEqual(
+ a_non_user_properties, b_non_user_properties);
+}
+
+void GetExtensionSpecifics(const Extension& extension,
+ ExtensionsService* extensions_service,
+ sync_pb::ExtensionSpecifics* specifics) {
+ const std::string& id = extension.id();
+ bool enabled =
+ extensions_service->GetExtensionById(id, false) != NULL;
+ bool incognito_enabled =
+ extensions_service->IsIncognitoEnabled(&extension);
+ GetExtensionSpecificsHelper(extension, enabled, incognito_enabled,
+ specifics);
+}
+
+void GetExtensionSpecificsHelper(const Extension& extension,
+ bool enabled, bool incognito_enabled,
+ sync_pb::ExtensionSpecifics* specifics) {
+ DCHECK(IsExtensionSyncable(extension));
+ const std::string& id = extension.id();
+ specifics->set_id(id);
+ specifics->set_version(extension.VersionString());
+ specifics->set_update_url(extension.update_url().spec());
+ specifics->set_enabled(enabled);
+ specifics->set_incognito_enabled(incognito_enabled);
+ specifics->set_name(extension.name());
+ DcheckIsExtensionSpecificsValid(*specifics);
+}
+
+bool IsExtensionOutdated(const Extension& extension,
+ const sync_pb::ExtensionSpecifics& specifics) {
+ DCHECK(IsExtensionSyncable(extension));
+ DcheckIsExtensionSpecificsValid(specifics);
+ scoped_ptr<Version> specifics_version(
+ Version::GetVersionFromString(specifics.version()));
+ if (!specifics_version.get()) {
+ // If version is invalid, assume we're up-to-date.
+ return false;
+ }
+ return extension.version()->CompareTo(*specifics_version) < 0;
+}
+
+void SetExtensionProperties(
+ const sync_pb::ExtensionSpecifics& specifics,
+ ExtensionsService* extensions_service, Extension* extension) {
+ DcheckIsExtensionSpecificsValid(specifics);
+ CHECK(extensions_service);
+ CHECK(extension);
+ DCHECK(IsExtensionSyncable(*extension));
+ const std::string& id = extension->id();
+ GURL update_url(specifics.update_url());
+ if (update_url != extension->update_url()) {
+ LOG(WARNING) << "specifics for extension " << id
+ << "has a different update URL than the extension: "
+ << update_url.spec() << " vs. " << extension->update_url();
+ }
+ bool enabled = extensions_service->GetExtensionById(id, false) != NULL;
+ if (enabled && !specifics.enabled()) {
+ extensions_service->DisableExtension(id);
+ } else if (!enabled && specifics.enabled()) {
+ extensions_service->EnableExtension(id);
+ }
+ bool incognito_enabled =
+ extensions_service->IsIncognitoEnabled(extension);
+ if (incognito_enabled != specifics.incognito_enabled()) {
+ extensions_service->SetIsIncognitoEnabled(
+ extension, specifics.incognito_enabled());
+ }
+ if (specifics.name() != extension->name()) {
+ LOG(WARNING) << "specifics for extension " << id
+ << "has a different name than the extension: "
+ << specifics.name() << " vs. " << extension->name();
+ }
+}
+
+void MergeExtensionSpecifics(
+ const sync_pb::ExtensionSpecifics& specifics,
+ bool merge_user_properties,
+ sync_pb::ExtensionSpecifics* merged_specifics) {
+ DcheckIsExtensionSpecificsValid(*merged_specifics);
+ DcheckIsExtensionSpecificsValid(specifics);
+ DCHECK_EQ(specifics.id(), merged_specifics->id());
+ // TODO(akalin): Merge enabled permissions when we sync those.
+ scoped_ptr<Version> version(
+ Version::GetVersionFromString(specifics.version()));
+ CHECK(version.get());
+ scoped_ptr<Version> merged_version(
+ Version::GetVersionFromString(merged_specifics->version()));
+ CHECK(merged_version.get());
+ if (version->CompareTo(*merged_version) >= 0) {
+ // |specifics| has a more recent or the same version, so merge it
+ // in.
+ CopyNonUserProperties(specifics, merged_specifics);
+ if (merge_user_properties) {
+ CopyUserProperties(specifics, merged_specifics);
+ }
+ }
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/extension_util.h b/chrome/browser/sync/glue/extension_util.h
new file mode 100644
index 0000000..0eef52f
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_util.h
@@ -0,0 +1,112 @@
+// 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_EXTENSION_UTIL_H_
+#define CHROME_BROWSER_SYNC_GLUE_EXTENSION_UTIL_H_
+
+#include <string>
+
+class Extension;
+class ExtensionsService;
+
+namespace sync_pb {
+class ExtensionSpecifics;
+} // sync_pb
+
+namespace browser_sync {
+
+// Returns whether or not the given extension is one we want to sync.
+bool IsExtensionSyncable(const Extension& extension);
+
+// Stringifies the given ExtensionSpecifics.
+std::string ExtensionSpecificsToString(
+ const sync_pb::ExtensionSpecifics& specifics);
+
+// Returns whether or not the values of the given specifics are valid,
+// in particular the id, version, and update URL.
+bool IsExtensionSpecificsValid(
+ const sync_pb::ExtensionSpecifics& specifics);
+
+// Equivalent to DCHECK(IsExtensionSpecificsValid(specifics)) <<
+// ExtensionSpecificsToString(specifics);
+void DcheckIsExtensionSpecificsValid(
+ const sync_pb::ExtensionSpecifics& specifics);
+
+// Returns true iff two ExtensionSpecifics denote the same extension
+// state. Neither |a| nor |b| need to be valid.
+bool AreExtensionSpecificsEqual(const sync_pb::ExtensionSpecifics& a,
+ const sync_pb::ExtensionSpecifics& b);
+
+// Returns true iff the given ExtensionSpecifics is equal to the empty
+// ExtensionSpecifics object. |specifics| does not have to be valid
+// and indeed, IsExtensionSpecificsValid(specifics) ->
+// !IsExtensionSpecificsUnset(specifics).
+bool IsExtensionSpecificsUnset(
+ const sync_pb::ExtensionSpecifics& specifics);
+
+// Copies the user properties from |specifics| into |dest_specifics|.
+// User properties are properties that are set by the user, i.e. not
+// inherent to the extension. Currently they include |enabled| and
+// |incognito_enabled|. Neither parameter need be valid.
+void CopyUserProperties(
+ const sync_pb::ExtensionSpecifics& specifics,
+ sync_pb::ExtensionSpecifics* dest_specifics);
+
+// Copies everything but non-user properties. Neither parameter need
+// be valid.
+void CopyNonUserProperties(
+ const sync_pb::ExtensionSpecifics& specifics,
+ sync_pb::ExtensionSpecifics* dest_specifics);
+
+// Returns true iff two ExtensionSpecifics have the same user
+// properties. Neither |a| nor |b| need to be valid.
+bool AreExtensionSpecificsUserPropertiesEqual(
+ const sync_pb::ExtensionSpecifics& a,
+ const sync_pb::ExtensionSpecifics& b);
+
+// Returns true iff two ExtensionSpecifics have the same non-user
+// properties. Neither |a| nor |b| need to be valid.
+bool AreExtensionSpecificsNonUserPropertiesEqual(
+ const sync_pb::ExtensionSpecifics& a,
+ const sync_pb::ExtensionSpecifics& b);
+
+// Fills |specifics| with information taken from |extension|, which
+// must be a syncable extension. |specifics| will be valid after this
+// function is called.
+void GetExtensionSpecifics(const Extension& extension,
+ ExtensionsService* extensions_service,
+ sync_pb::ExtensionSpecifics* specifics);
+
+// Exposed only for testing. Pre- and post-conditions are the same as
+// GetExtensionSpecifics().
+void GetExtensionSpecificsHelper(const Extension& extension,
+ bool enabled, bool incognito_enabled,
+ sync_pb::ExtensionSpecifics* specifics);
+
+// Returns whether or not the extension should be updated according to
+// the specifics. |extension| must be syncable and |specifics| must
+// be valid.
+bool IsExtensionOutdated(const Extension& extension,
+ const sync_pb::ExtensionSpecifics& specifics);
+
+// Sets properties of |extension| according to the information in
+// specifics. |extension| must be syncable and |specifics| must be
+// valid.
+void SetExtensionProperties(
+ const sync_pb::ExtensionSpecifics& specifics,
+ ExtensionsService* extensions_service, Extension* extension);
+
+// Merge |specifics| into |merged_specifics|. Both must be valid and
+// have the same ID. The merge policy is currently to copy the
+// non-user properties of |specifics| into |merged_specifics| (and the
+// user properties if |merge_user_properties| is set) if |specifics|
+// has a more recent or the same version as |merged_specifics|.
+void MergeExtensionSpecifics(
+ const sync_pb::ExtensionSpecifics& specifics,
+ bool merge_user_properties,
+ sync_pb::ExtensionSpecifics* merged_specifics);
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_UTIL_H_
diff --git a/chrome/browser/sync/glue/extension_util_unittest.cc b/chrome/browser/sync/glue/extension_util_unittest.cc
new file mode 100644
index 0000000..9094838
--- /dev/null
+++ b/chrome/browser/sync/glue/extension_util_unittest.cc
@@ -0,0 +1,420 @@
+// 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/extension_util.h"
+
+#include "base/file_path.h"
+#include "base/version.h"
+#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser_sync {
+
+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("/foo");
+#endif
+
+const char kValidId[] = "abcdefghijklmnopabcdefghijklmnop";
+const char kValidVersion[] = "0.0.0.0";
+const char kVersion1[] = "1.0.0.1";
+const char kVersion2[] = "1.0.1.0";
+const char kVersion3[] = "1.1.0.0";
+const char kValidUpdateUrl[] = "http://www.google.com/";
+const char kValidUpdateUrl1[] = "http://www.1.com/";
+const char kValidUpdateUrl2[] = "http://www.2.com/";
+const char kName[] = "MyExtension";
+const char kName2[] = "MyExtension2";
+
+class ExtensionUtilTest : public testing::Test {
+};
+
+void MakePossiblySyncableExtension(bool is_theme,
+ const GURL& launch_url,
+ Extension::Location location,
+ Extension* extension) {
+ DictionaryValue source;
+ source.SetString(extension_manifest_keys::kName,
+ "PossiblySyncableExtension");
+ source.SetString(extension_manifest_keys::kVersion, "0.0.0.0");
+ if (is_theme) {
+ source.Set(extension_manifest_keys::kTheme, new DictionaryValue());
+ }
+ if (!launch_url.is_empty()) {
+ source.SetString(extension_manifest_keys::kLaunchWebURL,
+ launch_url.spec());
+ }
+ std::string error;
+ EXPECT_TRUE(extension->InitFromValue(source, false, &error));
+ EXPECT_EQ("", error);
+ extension->set_location(location);
+}
+
+TEST_F(ExtensionUtilTest, IsSyncableExtension) {
+ {
+ FilePath file_path(kExtensionFilePath);
+ Extension extension(file_path);
+ MakePossiblySyncableExtension(false, GURL(), Extension::INTERNAL,
+ &extension);
+ EXPECT_TRUE(IsExtensionSyncable(extension));
+ }
+ {
+ FilePath file_path(kExtensionFilePath);
+ Extension extension(file_path);
+ MakePossiblySyncableExtension(true, GURL(), Extension::INTERNAL,
+ &extension);
+ EXPECT_FALSE(IsExtensionSyncable(extension));
+ }
+ // TODO(akalin): Test with a non-empty launch_url once apps are
+ // enabled by default.
+ {
+ FilePath file_path(kExtensionFilePath);
+ Extension extension(file_path);
+ MakePossiblySyncableExtension(false, GURL(), Extension::EXTERNAL_PREF,
+ &extension);
+ EXPECT_FALSE(IsExtensionSyncable(extension));
+ }
+}
+
+TEST_F(ExtensionUtilTest, IsExtensionSpecificsUnset) {
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ EXPECT_TRUE(IsExtensionSpecificsUnset(specifics));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_id("a");
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_version("a");
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_update_url("a");
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_enabled(true);
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_incognito_enabled(true);
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_name("a");
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ }
+}
+
+TEST_F(ExtensionUtilTest, IsExtensionSpecificsValid) {
+ sync_pb::ExtensionSpecifics specifics;
+ EXPECT_FALSE(IsExtensionSpecificsValid(specifics));
+ specifics.set_id(kValidId);
+ EXPECT_FALSE(IsExtensionSpecificsValid(specifics));
+ specifics.set_version(kValidVersion);
+ EXPECT_TRUE(IsExtensionSpecificsValid(specifics));
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+ specifics.set_update_url(kValidUpdateUrl);
+ EXPECT_TRUE(IsExtensionSpecificsValid(specifics));
+ EXPECT_FALSE(IsExtensionSpecificsUnset(specifics));
+
+ {
+ sync_pb::ExtensionSpecifics specifics_copy(specifics);
+ specifics_copy.set_id("invalid");
+ EXPECT_FALSE(IsExtensionSpecificsValid(specifics_copy));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics_copy(specifics);
+ specifics_copy.set_version("invalid");
+ EXPECT_FALSE(IsExtensionSpecificsValid(specifics_copy));
+ }
+
+ {
+ sync_pb::ExtensionSpecifics specifics_copy(specifics);
+ specifics_copy.set_update_url("http:invalid.com:invalid");
+ EXPECT_FALSE(IsExtensionSpecificsValid(specifics_copy));
+ }
+}
+
+TEST_F(ExtensionUtilTest, AreExtensionSpecificsEqual) {
+ sync_pb::ExtensionSpecifics a, b;
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+
+ a.set_id("a");
+ EXPECT_FALSE(AreExtensionSpecificsEqual(a, b));
+ b.set_id("a");
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+
+ a.set_version("1.5");
+ EXPECT_FALSE(AreExtensionSpecificsEqual(a, b));
+ b.set_version("1.5");
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+
+ a.set_update_url("http://www.foo.com");
+ EXPECT_FALSE(AreExtensionSpecificsEqual(a, b));
+ b.set_update_url("http://www.foo.com");
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+
+ a.set_enabled(true);
+ EXPECT_FALSE(AreExtensionSpecificsEqual(a, b));
+ b.set_enabled(true);
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+
+ a.set_incognito_enabled(true);
+ EXPECT_FALSE(AreExtensionSpecificsEqual(a, b));
+ b.set_incognito_enabled(true);
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+
+ a.set_name("name");
+ EXPECT_FALSE(AreExtensionSpecificsEqual(a, b));
+ b.set_name("name");
+ EXPECT_TRUE(AreExtensionSpecificsEqual(a, b));
+}
+
+TEST_F(ExtensionUtilTest, CopyUserProperties) {
+ sync_pb::ExtensionSpecifics dest_specifics;
+ dest_specifics.set_version(kVersion2);
+ dest_specifics.set_update_url(kValidUpdateUrl1);
+ dest_specifics.set_enabled(true);
+ dest_specifics.set_incognito_enabled(false);
+ dest_specifics.set_name(kName);
+
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_id(kValidId);
+ specifics.set_version(kVersion3);
+ specifics.set_update_url(kValidUpdateUrl2);
+ specifics.set_enabled(false);
+ specifics.set_incognito_enabled(true);
+ specifics.set_name(kName2);
+
+ CopyUserProperties(specifics, &dest_specifics);
+ EXPECT_EQ("", dest_specifics.id());
+ EXPECT_EQ(kVersion2, dest_specifics.version());
+ EXPECT_EQ(kValidUpdateUrl1, dest_specifics.update_url());
+ EXPECT_FALSE(dest_specifics.enabled());
+ EXPECT_TRUE(dest_specifics.incognito_enabled());
+ EXPECT_EQ(kName, dest_specifics.name());
+}
+
+TEST_F(ExtensionUtilTest, CopyNonUserProperties) {
+ sync_pb::ExtensionSpecifics dest_specifics;
+ dest_specifics.set_id(kValidId);
+ dest_specifics.set_version(kVersion2);
+ dest_specifics.set_update_url(kValidUpdateUrl1);
+ dest_specifics.set_enabled(true);
+ dest_specifics.set_incognito_enabled(false);
+ dest_specifics.set_name(kName);
+
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_id("");
+ specifics.set_version(kVersion3);
+ specifics.set_update_url(kValidUpdateUrl2);
+ specifics.set_enabled(false);
+ specifics.set_incognito_enabled(true);
+ specifics.set_name(kName2);
+
+ CopyNonUserProperties(specifics, &dest_specifics);
+ EXPECT_EQ("", dest_specifics.id());
+ EXPECT_EQ(kVersion3, dest_specifics.version());
+ EXPECT_EQ(kValidUpdateUrl2, dest_specifics.update_url());
+ EXPECT_TRUE(dest_specifics.enabled());
+ EXPECT_FALSE(dest_specifics.incognito_enabled());
+ EXPECT_EQ(kName2, dest_specifics.name());
+}
+
+TEST_F(ExtensionUtilTest, AreExtensionSpecificsUserPropertiesEqual) {
+ sync_pb::ExtensionSpecifics a, b;
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+
+ a.set_id("a");
+ b.set_id("b");
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+
+ a.set_version("1.5");
+ b.set_version("1.6");
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+
+ a.set_name("name");
+ b.set_name("name2");
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+
+ a.set_update_url("http://www.foo.com");
+ b.set_update_url("http://www.foo2.com");
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+
+ a.set_enabled(true);
+ EXPECT_FALSE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+ b.set_enabled(true);
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+
+ a.set_incognito_enabled(true);
+ EXPECT_FALSE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+ b.set_incognito_enabled(true);
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(a, b));
+}
+
+TEST_F(ExtensionUtilTest, AreExtensionSpecificsNonUserPropertiesEqual) {
+ sync_pb::ExtensionSpecifics a, b;
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+
+ a.set_enabled(true);
+ b.set_enabled(false);
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+
+ a.set_incognito_enabled(true);
+ b.set_incognito_enabled(false);
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+
+ a.set_id("a");
+ EXPECT_FALSE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+ b.set_id("a");
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+
+ a.set_version("1.5");
+ EXPECT_FALSE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+ b.set_version("1.5");
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+
+ a.set_update_url("http://www.foo.com");
+ EXPECT_FALSE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+ b.set_update_url("http://www.foo.com");
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+
+ a.set_name("name");
+ EXPECT_FALSE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+ b.set_name("name");
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(a, b));
+}
+
+void MakeSyncableExtension(const std::string& version_string,
+ const std::string& update_url_spec,
+ const std::string& name,
+ Extension* extension) {
+ DictionaryValue source;
+ source.SetString(extension_manifest_keys::kVersion, version_string);
+ source.SetString(extension_manifest_keys::kUpdateURL, update_url_spec);
+ source.SetString(extension_manifest_keys::kName, name);
+ std::string error;
+ EXPECT_TRUE(extension->InitFromValue(source, false, &error));
+ EXPECT_EQ("", error);
+ extension->set_location(Extension::INTERNAL);
+}
+
+TEST_F(ExtensionUtilTest, GetExtensionSpecificsHelper) {
+ FilePath file_path(kExtensionFilePath);
+ Extension extension(file_path);
+ MakeSyncableExtension(kValidVersion, kValidUpdateUrl, kName,
+ &extension);
+ sync_pb::ExtensionSpecifics specifics;
+ GetExtensionSpecificsHelper(extension, true, false, &specifics);
+ EXPECT_EQ(extension.id(), specifics.id());
+ EXPECT_EQ(extension.VersionString(), kValidVersion);
+ EXPECT_EQ(extension.update_url().spec(), kValidUpdateUrl);
+ EXPECT_TRUE(specifics.enabled());
+ EXPECT_FALSE(specifics.incognito_enabled());
+ EXPECT_EQ(kName, specifics.name());
+}
+
+TEST_F(ExtensionUtilTest, IsExtensionOutdated) {
+ FilePath file_path(kExtensionFilePath);
+ Extension extension(file_path);
+ MakeSyncableExtension(kVersion2, kValidUpdateUrl, kName,
+ &extension);
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_id(kValidId);
+ specifics.set_update_url(kValidUpdateUrl);
+
+ specifics.set_version(kVersion1);
+ EXPECT_FALSE(IsExtensionOutdated(extension, specifics));
+ specifics.set_version(kVersion2);
+ EXPECT_FALSE(IsExtensionOutdated(extension, specifics));
+ specifics.set_version(kVersion3);
+ EXPECT_TRUE(IsExtensionOutdated(extension, specifics));
+}
+
+// TODO(akalin): Make ExtensionsService/ExtensionUpdater testable
+// enough to be able to write a unittest for SetExtensionProperties().
+
+TEST_F(ExtensionUtilTest, MergeExtensionSpecificsWithUserProperties) {
+ sync_pb::ExtensionSpecifics merged_specifics;
+ merged_specifics.set_id(kValidId);
+ merged_specifics.set_update_url(kValidUpdateUrl1);
+ merged_specifics.set_enabled(true);
+ merged_specifics.set_incognito_enabled(false);
+ merged_specifics.set_version(kVersion2);
+
+ sync_pb::ExtensionSpecifics specifics;
+ specifics.set_id(kValidId);
+ specifics.set_update_url(kValidUpdateUrl2);
+ merged_specifics.set_enabled(false);
+ merged_specifics.set_incognito_enabled(true);
+
+ specifics.set_version(kVersion1);
+ {
+ sync_pb::ExtensionSpecifics result = merged_specifics;
+ MergeExtensionSpecifics(specifics, false, &result);
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(
+ result, merged_specifics));
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(
+ result, merged_specifics));
+ }
+ {
+ sync_pb::ExtensionSpecifics result = merged_specifics;
+ MergeExtensionSpecifics(specifics, true, &result);
+ EXPECT_TRUE(AreExtensionSpecificsEqual(result, merged_specifics));
+ }
+
+ specifics.set_version(kVersion2);
+ {
+ sync_pb::ExtensionSpecifics result = merged_specifics;
+ MergeExtensionSpecifics(specifics, false, &result);
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(
+ result, merged_specifics));
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(
+ result, specifics));
+ }
+ {
+ sync_pb::ExtensionSpecifics result = merged_specifics;
+ MergeExtensionSpecifics(specifics, true, &result);
+ EXPECT_TRUE(AreExtensionSpecificsEqual(result, specifics));
+ }
+
+ specifics.set_version(kVersion3);
+ {
+ sync_pb::ExtensionSpecifics result = merged_specifics;
+ MergeExtensionSpecifics(specifics, false, &result);
+ EXPECT_TRUE(AreExtensionSpecificsUserPropertiesEqual(
+ result, merged_specifics));
+ EXPECT_TRUE(AreExtensionSpecificsNonUserPropertiesEqual(
+ result, specifics));
+ }
+ {
+ sync_pb::ExtensionSpecifics result = merged_specifics;
+ MergeExtensionSpecifics(specifics, true, &result);
+ EXPECT_TRUE(AreExtensionSpecificsEqual(result, specifics));
+ }
+}
+
+} // namespace
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h
index d2cb6e5..f8b16e7 100644
--- a/chrome/browser/sync/profile_sync_factory.h
+++ b/chrome/browser/sync/profile_sync_factory.h
@@ -73,6 +73,13 @@ class ProfileSyncFactory {
browser_sync::UnrecoverableErrorHandler* error_handler) = 0;
// Instantiates both a model associator and change processor for the
+ // extension data type. The pointers in the return struct are
+ // owned by the caller.
+ virtual SyncComponents CreateExtensionSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ browser_sync::UnrecoverableErrorHandler* error_handler) = 0;
+
+ // Instantiates both a model associator and change processor for the
// password data type. The pointers in the return struct are
// owned by the caller.
virtual SyncComponents CreatePasswordSyncComponents(
diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc
index 0c75936..f0d698f 100644
--- a/chrome/browser/sync/profile_sync_factory_impl.cc
+++ b/chrome/browser/sync/profile_sync_factory_impl.cc
@@ -13,6 +13,9 @@
#include "chrome/browser/sync/glue/bookmark_data_type_controller.h"
#include "chrome/browser/sync/glue/bookmark_model_associator.h"
#include "chrome/browser/sync/glue/data_type_manager_impl.h"
+#include "chrome/browser/sync/glue/extension_change_processor.h"
+#include "chrome/browser/sync/glue/extension_data_type_controller.h"
+#include "chrome/browser/sync/glue/extension_model_associator.h"
#include "chrome/browser/sync/glue/password_change_processor.h"
#include "chrome/browser/sync/glue/password_data_type_controller.h"
#include "chrome/browser/sync/glue/password_model_associator.h"
@@ -40,6 +43,9 @@ using browser_sync::BookmarkModelAssociator;
using browser_sync::DataTypeController;
using browser_sync::DataTypeManager;
using browser_sync::DataTypeManagerImpl;
+using browser_sync::ExtensionChangeProcessor;
+using browser_sync::ExtensionDataTypeController;
+using browser_sync::ExtensionModelAssociator;
using browser_sync::PasswordChangeProcessor;
using browser_sync::PasswordDataTypeController;
using browser_sync::PasswordModelAssociator;
@@ -88,6 +94,13 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService() {
new BookmarkDataTypeController(this, profile_, pss));
}
+ // Extension sync is disabled by default. Register only if
+ // explicitly enabled.
+ if (command_line_->HasSwitch(switches::kEnableSyncExtensions)) {
+ pss->RegisterDataTypeController(
+ new ExtensionDataTypeController(this, profile_, pss));
+ }
+
// Password sync is disabled by default. Register only if
// explicitly enabled.
if (command_line_->HasSwitch(switches::kEnableSyncPasswords)) {
@@ -156,6 +169,17 @@ ProfileSyncFactoryImpl::CreateBookmarkSyncComponents(
}
ProfileSyncFactory::SyncComponents
+ProfileSyncFactoryImpl::CreateExtensionSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ UnrecoverableErrorHandler* error_handler) {
+ ExtensionModelAssociator* model_associator =
+ new ExtensionModelAssociator(profile_sync_service);
+ ExtensionChangeProcessor* change_processor =
+ new ExtensionChangeProcessor(error_handler, model_associator);
+ return SyncComponents(model_associator, change_processor);
+}
+
+ProfileSyncFactory::SyncComponents
ProfileSyncFactoryImpl::CreatePasswordSyncComponents(
ProfileSyncService* profile_sync_service,
PasswordStore* password_store,
diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h
index bbce500..c26ba4f 100644
--- a/chrome/browser/sync/profile_sync_factory_impl.h
+++ b/chrome/browser/sync/profile_sync_factory_impl.h
@@ -41,6 +41,10 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory {
ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler);
+ virtual SyncComponents CreateExtensionSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ browser_sync::UnrecoverableErrorHandler* error_handler);
+
virtual SyncComponents CreatePasswordSyncComponents(
ProfileSyncService* profile_sync_service,
PasswordStore* password_store,
diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h
index df760c5..a323b34 100644
--- a/chrome/browser/sync/profile_sync_factory_mock.h
+++ b/chrome/browser/sync/profile_sync_factory_mock.h
@@ -37,6 +37,9 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory {
MOCK_METHOD2(CreateBookmarkSyncComponents,
SyncComponents(ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler));
+ MOCK_METHOD2(CreateExtensionSyncComponents,
+ SyncComponents(ProfileSyncService* profile_sync_service,
+ browser_sync::UnrecoverableErrorHandler* error_handler));
MOCK_METHOD3(CreatePasswordSyncComponents,
SyncComponents(
ProfileSyncService* profile_sync_service,
diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc
index 204f65d..8aefe40 100644
--- a/chrome/browser/sync/syncable/model_type.cc
+++ b/chrome/browser/sync/syncable/model_type.cc
@@ -113,6 +113,8 @@ std::string ModelTypeToString(ModelType model_type) {
return "Themes";
case TYPED_URLS:
return "Typed URLs";
+ case EXTENSIONS:
+ return "Extensions";
default:
NOTREACHED() << "No known extension for model type.";
return "INVALID";
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 9e80c17..2535b6d 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2105,6 +2105,16 @@
'browser/sync/glue/data_type_manager_impl.h',
'browser/sync/glue/database_model_worker.cc',
'browser/sync/glue/database_model_worker.h',
+ 'browser/sync/glue/extension_change_processor.cc',
+ 'browser/sync/glue/extension_change_processor.h',
+ 'browser/sync/glue/extension_data.cc',
+ 'browser/sync/glue/extension_data.h',
+ 'browser/sync/glue/extension_data_type_controller.cc',
+ 'browser/sync/glue/extension_data_type_controller.h',
+ 'browser/sync/glue/extension_model_associator.cc',
+ 'browser/sync/glue/extension_model_associator.h',
+ 'browser/sync/glue/extension_util.cc',
+ 'browser/sync/glue/extension_util.h',
'browser/sync/glue/history_model_worker.cc',
'browser/sync/glue/history_model_worker.h',
'browser/sync/glue/password_model_worker.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 6c61324..7786f58 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -918,6 +918,9 @@
'browser/sync/glue/data_type_manager_impl_unittest.cc',
'browser/sync/glue/data_type_manager_mock.h',
'browser/sync/glue/database_model_worker_unittest.cc',
+ 'browser/sync/glue/extension_data_unittest.cc',
+ 'browser/sync/glue/extension_data_type_controller_unittest.cc',
+ 'browser/sync/glue/extension_util_unittest.cc',
'browser/sync/glue/http_bridge_unittest.cc',
'browser/sync/glue/preference_data_type_controller_unittest.cc',
'browser/sync/glue/sync_backend_host_mock.h',