diff options
50 files changed, 1967 insertions, 121 deletions
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index bf77408..d18e004 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -581,14 +581,14 @@ ExtensionService::ExtensionService(Profile* profile, const CommandLine* command_line, const FilePath& install_directory, ExtensionPrefs* extension_prefs, - ExtensionSettings* extension_settings, bool autoupdate_enabled, bool extensions_enabled) : weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), profile_(profile), extension_prefs_(extension_prefs), - extension_settings_(extension_settings), + extension_settings_( + profile->GetPath().AppendASCII(kSettingsDirectoryName)), pending_extension_manager_(*ALLOW_THIS_IN_INITIALIZER_LIST(this)), install_directory_(install_directory), extensions_enabled_(extensions_enabled), @@ -1664,8 +1664,8 @@ ExtensionPrefs* ExtensionService::extension_prefs() { return extension_prefs_; } -ExtensionSettings* ExtensionService::extension_settings() { - return extension_settings_; +ExtensionSettingsUIWrapper* ExtensionService::extension_settings() { + return &extension_settings_; } ExtensionContentSettingsStore* diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index 51e2d91..ffa598b 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -28,6 +28,7 @@ #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_permissions_api.h" #include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extension_settings_ui_wrapper.h" #include "chrome/browser/extensions/extension_sync_data.h" #include "chrome/browser/extensions/extension_toolbar_model.h" #include "chrome/browser/extensions/extensions_quota_service.h" @@ -56,7 +57,6 @@ class ExtensionInstallUI; class ExtensionManagementEventRouter; class ExtensionPreferenceEventRouter; class ExtensionServiceBackend; -class ExtensionSettings; class ExtensionSyncData; class ExtensionToolbarModel; class ExtensionUpdater; @@ -179,7 +179,6 @@ class ExtensionService const CommandLine* command_line, const FilePath& install_directory, ExtensionPrefs* extension_prefs, - ExtensionSettings* extension_settings, bool autoupdate_enabled, bool extensions_enabled); @@ -440,7 +439,7 @@ class ExtensionService // ExtensionPrefs* mutable_extension_prefs(). ExtensionPrefs* extension_prefs(); - ExtensionSettings* extension_settings(); + ExtensionSettingsUIWrapper* extension_settings(); ExtensionContentSettingsStore* GetExtensionContentSettingsStore(); @@ -669,8 +668,8 @@ class ExtensionService // Preferences for the owning profile (weak reference). ExtensionPrefs* extension_prefs_; - // Settings for the owning profile (weak reference). - ExtensionSettings* extension_settings_; + // Settings for the owning profile. + ExtensionSettingsUIWrapper extension_settings_; // The current list of installed extensions. // TODO(aa): This should use chrome/common/extensions/extension_set.h. diff --git a/chrome/browser/extensions/extension_setting_sync_data.cc b/chrome/browser/extensions/extension_setting_sync_data.cc new file mode 100644 index 0000000..96be9a1 --- /dev/null +++ b/chrome/browser/extensions/extension_setting_sync_data.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2011 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/extensions/extension_setting_sync_data.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "chrome/browser/sync/api/sync_data.h" +#include "chrome/browser/sync/protocol/extension_setting_specifics.pb.h" + +ExtensionSettingSyncData::ExtensionSettingSyncData( + const SyncChange& sync_change) { + Init(sync_change.change_type(), sync_change.sync_data()); +} + +ExtensionSettingSyncData::ExtensionSettingSyncData( + const SyncData& sync_data) { + Init(SyncChange::ACTION_INVALID, sync_data); +} + +void ExtensionSettingSyncData::Init( + SyncChange::SyncChangeType change_type, const SyncData& sync_data) { + DCHECK(!internal_.get()); + sync_pb::ExtensionSettingSpecifics specifics = + sync_data.GetSpecifics().GetExtension(sync_pb::extension_setting); + Value* value = + base::JSONReader().JsonToValue(specifics.value(), false, false); + if (!value) { + LOG(WARNING) << "Specifics for " << specifics.extension_id() << "/" << + specifics.key() << " had bad JSON for value: " << specifics.value(); + value = new DictionaryValue(); + } + internal_ = new Internal( + change_type, + specifics.extension_id(), + specifics.key(), + value); +} + +ExtensionSettingSyncData::ExtensionSettingSyncData( + SyncChange::SyncChangeType change_type, + const std::string& extension_id, + const std::string& key, + Value* value) + : internal_(new Internal(change_type, extension_id, key, value)) { + CHECK(value); +} + +ExtensionSettingSyncData::~ExtensionSettingSyncData() {} + +SyncChange::SyncChangeType ExtensionSettingSyncData::change_type() const { + return internal_->change_type_; +} + +const std::string& ExtensionSettingSyncData::extension_id() const { + return internal_->extension_id_; +} + +const std::string& ExtensionSettingSyncData::key() const { + return internal_->key_; +} + +const Value& ExtensionSettingSyncData::value() const { + return *internal_->value_; +} + +ExtensionSettingSyncData::Internal::Internal( + SyncChange::SyncChangeType change_type, + const std::string& extension_id, + const std::string& key, + Value* value) + : change_type_(change_type), + extension_id_(extension_id), + key_(key), + value_(value) {} + +ExtensionSettingSyncData::Internal::~Internal() {} diff --git a/chrome/browser/extensions/extension_setting_sync_data.h b/chrome/browser/extensions/extension_setting_sync_data.h new file mode 100644 index 0000000..48d6d6f --- /dev/null +++ b/chrome/browser/extensions/extension_setting_sync_data.h @@ -0,0 +1,77 @@ +// Copyright (c) 2011 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_EXTENSIONS_EXTENSION_SETTING_SYNC_DATA_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTING_SYNC_DATA_H_ +#pragma once + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/sync/api/sync_change.h" + +class SyncData; + +// Container for data interpreted from sync data/changes. Safe and efficient +// to copy. +class ExtensionSettingSyncData { + public: + // Creates from a sync change. + explicit ExtensionSettingSyncData(const SyncChange& sync_change); + + // Creates from sync data. change_type will be ACTION_INVALID. + explicit ExtensionSettingSyncData(const SyncData& sync_data); + + // Creates explicitly. + ExtensionSettingSyncData( + SyncChange::SyncChangeType change_type, + const std::string& extension_id, + const std::string& key, + // May NOT be NULL. Ownership taken. + Value* value); + + ~ExtensionSettingSyncData(); + + // Returns the type of the sync change; may be ACTION_INVALID. + SyncChange::SyncChangeType change_type() const; + + // Returns the extension id the setting is for. + const std::string& extension_id() const; + + // Returns the settings key. + const std::string& key() const; + + // Returns the value of the setting. + const Value& value() const; + + private: + // Ref-counted container for the data. + // TODO(kalman): Use browser_sync::Immutable<Internal>. + class Internal : public base::RefCountedThreadSafe<Internal> { + public: + explicit Internal( + SyncChange::SyncChangeType change_type, + const std::string& extension_id, + const std::string& key, + Value* value); + + SyncChange::SyncChangeType change_type_; + std::string extension_id_; + std::string key_; + scoped_ptr<Value> value_; + + private: + friend class base::RefCountedThreadSafe<Internal>; + ~Internal(); + }; + + // Initializes internal_ from sync data. + void Init(SyncChange::SyncChangeType change_type, const SyncData& sync_data); + + scoped_refptr<Internal> internal_; +}; + +typedef std::vector<ExtensionSettingSyncData> ExtensionSettingSyncDataList; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTING_SYNC_DATA_H_ diff --git a/chrome/browser/extensions/extension_settings.cc b/chrome/browser/extensions/extension_settings.cc index 0d855b95..9349ad06 100644 --- a/chrome/browser/extensions/extension_settings.cc +++ b/chrome/browser/extensions/extension_settings.cc @@ -4,76 +4,95 @@ #include "chrome/browser/extensions/extension_settings.h" -#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" +#include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" -#include "content/browser/browser_thread.h" #include "chrome/browser/extensions/extension_settings_leveldb_storage.h" #include "chrome/browser/extensions/extension_settings_noop_storage.h" #include "chrome/browser/extensions/extension_settings_storage_cache.h" +#include "chrome/browser/extensions/extension_settings_sync_util.h" +#include "chrome/common/extensions/extension.h" +#include "content/browser/browser_thread.h" #include "third_party/leveldatabase/src/include/leveldb/iterator.h" #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" ExtensionSettings::ExtensionSettings(const FilePath& base_path) - : base_path_(base_path) {} + : base_path_(base_path), + sync_processor_(NULL) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); +} ExtensionSettings::~ExtensionSettings() { - std::map<std::string, ExtensionSettingsStorage*>::iterator it; - for (it = storage_objs_.begin(); it != storage_objs_.end(); ++it) { - BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, it->second); - } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); } ExtensionSettingsStorage* ExtensionSettings::GetStorage( - const std::string& extension_id) { + const std::string& extension_id) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DictionaryValue empty; + return GetOrCreateStorageWithSyncData(extension_id, empty); +} - std::map<std::string, ExtensionSettingsStorage*>::iterator existing = - storage_objs_.find(extension_id); - if (existing != storage_objs_.end()) { - return existing->second; - } - - ExtensionSettingsStorage* new_storage = - CreateStorage(extension_id, ExtensionSettingsStorage::LEVELDB, true); - if (new_storage == NULL) { - // Failed to create a leveldb storage for some reason. Use an in memory - // storage area (no-op wrapped in a cache) instead. - new_storage = - CreateStorage(extension_id, ExtensionSettingsStorage::NOOP, true); - DCHECK(new_storage != NULL); +SyncableExtensionSettingsStorage* +ExtensionSettings::GetOrCreateStorageWithSyncData( + const std::string& extension_id, const DictionaryValue& sync_data) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + SyncableExtensionSettingsStorage* storage = GetOrCreateAndInitStorage( + ExtensionSettingsStorage::LEVELDB, + true, + extension_id, + sync_data); + if (!storage) { + // Fall back to an in-memory storage area (cached NOOP). + // It's ok for these to be synced, it just means that on next starting up + // extensions will see the "old" settings, then overwritten (and notified) + // when the sync changes come through. + storage = GetOrCreateAndInitStorage( + ExtensionSettingsStorage::NOOP, + true, + extension_id, + sync_data); + DCHECK(storage); } - - storage_objs_[extension_id] = new_storage; - return new_storage; + return storage; } ExtensionSettingsStorage* ExtensionSettings::GetStorageForTesting( ExtensionSettingsStorage::Type type, bool cached, - const std::string& extension_id) { + const std::string& extension_id) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DictionaryValue empty; + ExtensionSettingsStorage* storage = + GetOrCreateAndInitStorage(type, cached, extension_id, empty); + DCHECK(storage); + return storage; +} - std::map<std::string, ExtensionSettingsStorage*>::iterator existing = - storage_objs_.find(extension_id); +SyncableExtensionSettingsStorage* ExtensionSettings::GetOrCreateAndInitStorage( + ExtensionSettingsStorage::Type type, + bool cached, + const std::string& extension_id, + const DictionaryValue& initial_sync_data) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + StorageObjMap::iterator existing = storage_objs_.find(extension_id); if (existing != storage_objs_.end()) { - return existing->second; + return existing->second.get(); } - - ExtensionSettingsStorage* new_storage = - CreateStorage(extension_id, type, cached); - DCHECK(new_storage != NULL); - storage_objs_[extension_id] = new_storage; - return new_storage; + return CreateAndInitStorage(type, cached, extension_id, initial_sync_data); } -ExtensionSettingsStorage* ExtensionSettings::CreateStorage( - const std::string& extension_id, +SyncableExtensionSettingsStorage* ExtensionSettings::CreateAndInitStorage( ExtensionSettingsStorage::Type type, - bool cached) { + bool cached, + const std::string& extension_id, + const DictionaryValue& initial_sync_data) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK_EQ(0u, storage_objs_.count(extension_id)); ExtensionSettingsStorage* storage = NULL; switch (type) { case ExtensionSettingsStorage::NOOP: @@ -86,8 +105,178 @@ ExtensionSettingsStorage* ExtensionSettings::CreateStorage( default: NOTREACHED(); } - if (storage != NULL && cached) { + if (!storage) { + return NULL; + } + if (cached) { storage = new ExtensionSettingsStorageCache(storage); } - return storage; + + SyncableExtensionSettingsStorage* synced_storage = + new SyncableExtensionSettingsStorage(extension_id, storage); + if (sync_processor_) { + // TODO(kalman): do something if StartSyncing fails. + ignore_result( + synced_storage->StartSyncing(initial_sync_data, sync_processor_)); + } + storage_objs_[extension_id] = + linked_ptr<SyncableExtensionSettingsStorage>(synced_storage); + return synced_storage; +} + +std::set<std::string> ExtensionSettings::GetKnownExtensionIDs() const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + std::set<std::string> result; + + // TODO(kalman): we will need to do something to disambiguate between app + // settings and extension settings, since settings for apps should be synced + // iff app sync is turned on, ditto for extensions. + + // Extension IDs live in-memory and/or on disk. The cache will contain all + // that are in-memory. + for (StorageObjMap::iterator it = storage_objs_.begin(); + it != storage_objs_.end(); ++it) { + result.insert(it->first); + } + + // Leveldb databases are directories inside base_path_. + file_util::FileEnumerator::FindInfo find_info; + file_util::FileEnumerator extension_dirs( + base_path_, false, file_util::FileEnumerator::DIRECTORIES); + while (!extension_dirs.Next().empty()) { + extension_dirs.GetFindInfo(&find_info); + FilePath extension_dir(file_util::FileEnumerator::GetFilename(find_info)); + DCHECK(!extension_dir.IsAbsolute()); + // Extension IDs are created as std::strings so they *should* be ASCII. + std::string maybe_as_ascii(extension_dir.MaybeAsASCII()); + if (!maybe_as_ascii.empty()) { + result.insert(maybe_as_ascii); + } + } + + return result; +} + +SyncDataList ExtensionSettings::GetAllSyncData( + syncable::ModelType type) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK_EQ(type, syncable::EXTENSION_SETTINGS); + + // For all extensions, get all their settings. + // This has the effect of bringing in the entire state of extension settings + // in memory; sad. + SyncDataList all_sync_data; + std::set<std::string> known_extension_ids(GetKnownExtensionIDs()); + + for (std::set<std::string>::const_iterator it = known_extension_ids.begin(); + it != known_extension_ids.end(); ++it) { + ExtensionSettingsStorage::Result maybe_settings = GetStorage(*it)->Get(); + if (maybe_settings.HasError()) { + LOG(WARNING) << "Failed to get settings for " << *it << ": " << + maybe_settings.GetError(); + continue; + } + + DictionaryValue* settings = maybe_settings.GetSettings(); + for (DictionaryValue::key_iterator key_it = settings->begin_keys(); + key_it != settings->end_keys(); ++key_it) { + Value *value = NULL; + settings->GetWithoutPathExpansion(*key_it, &value); + all_sync_data.push_back( + extension_settings_sync_util::CreateData(*it, *key_it, *value)); + } + } + + return all_sync_data; +} + +SyncError ExtensionSettings::MergeDataAndStartSyncing( + syncable::ModelType type, + const SyncDataList& initial_sync_data, + SyncChangeProcessor* sync_processor) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK_EQ(type, syncable::EXTENSION_SETTINGS); + DCHECK(!sync_processor_); + sync_processor_ = sync_processor; + + // Group the initial sync data by extension id. + std::map<std::string, linked_ptr<DictionaryValue> > grouped_sync_data; + for (SyncDataList::const_iterator it = initial_sync_data.begin(); + it != initial_sync_data.end(); ++it) { + ExtensionSettingSyncData data(*it); + linked_ptr<DictionaryValue> sync_data = + grouped_sync_data[data.extension_id()]; + if (!sync_data.get()) { + sync_data = linked_ptr<DictionaryValue>(new DictionaryValue()); + grouped_sync_data[data.extension_id()] = sync_data; + } + DCHECK(!sync_data->HasKey(data.key())) << + "Duplicate settings for " << data.extension_id() << "/" << data.key(); + sync_data->Set(data.key(), data.value().DeepCopy()); + } + + // Start syncing all existing storage areas. Any storage areas created in + // the future will start being synced as part of the creation process. + for (StorageObjMap::iterator it = storage_objs_.begin(); + it != storage_objs_.end(); ++it) { + std::map<std::string, linked_ptr<DictionaryValue> >::iterator + maybe_sync_data = grouped_sync_data.find(it->first); + if (maybe_sync_data != grouped_sync_data.end()) { + // TODO(kalman): do something if StartSyncing fails. + ignore_result( + it->second->StartSyncing(*maybe_sync_data->second, sync_processor)); + grouped_sync_data.erase(it->first); + } else { + DictionaryValue empty; + // TODO(kalman): do something if StartSyncing fails. + ignore_result(it->second->StartSyncing(empty, sync_processor)); + } + } + + // Eagerly create and init the rest of the storage areas that have sync data. + // Under normal circumstances (i.e. not first-time sync) this will be all of + // them. + for (std::map<std::string, linked_ptr<DictionaryValue> >::iterator it = + grouped_sync_data.begin(); it != grouped_sync_data.end(); ++it) { + GetOrCreateStorageWithSyncData(it->first, *it->second); + } + + return SyncError(); +} + +SyncError ExtensionSettings::ProcessSyncChanges( + const tracked_objects::Location& from_here, + const SyncChangeList& sync_changes) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(sync_processor_); + + // Group changes by extension, to pass all changes in a single method call. + std::map<std::string, ExtensionSettingSyncDataList> grouped_sync_data; + for (SyncChangeList::const_iterator it = sync_changes.begin(); + it != sync_changes.end(); ++it) { + ExtensionSettingSyncData data(*it); + grouped_sync_data[data.extension_id()].push_back(data); + } + + DictionaryValue empty; + for (std::map<std::string, ExtensionSettingSyncDataList>::iterator + it = grouped_sync_data.begin(); it != grouped_sync_data.end(); ++it) { + // TODO(kalman): do something if ProcessSyncChanges fails. + ignore_result( + GetOrCreateStorageWithSyncData(it->first, empty)-> + ProcessSyncChanges(it->second)); + } + + return SyncError(); +} + +void ExtensionSettings::StopSyncing(syncable::ModelType type) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(sync_processor_); + sync_processor_ = NULL; + + for (StorageObjMap::iterator it = storage_objs_.begin(); + it != storage_objs_.end(); ++it) { + it->second->StopSyncing(); + } } diff --git a/chrome/browser/extensions/extension_settings.h b/chrome/browser/extensions/extension_settings.h index 1510a53..b6f87d8 100644 --- a/chrome/browser/extensions/extension_settings.h +++ b/chrome/browser/extensions/extension_settings.h @@ -6,23 +6,32 @@ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_H_ #pragma once +#include "base/compiler_specific.h" #include "base/file_path.h" +#include "base/memory/linked_ptr.h" #include "base/memory/ref_counted.h" -#include "chrome/browser/extensions/extension_settings_storage.h" +#include "base/task.h" +#include "chrome/browser/extensions/syncable_extension_settings_storage.h" +#include "chrome/browser/sync/api/syncable_service.h" -// Manages ExtensionSettingsStorage objects for extensions. -class ExtensionSettings : public base::RefCountedThreadSafe<ExtensionSettings> { +// Manages ExtensionSettingsStorage objects for extensions, including routing +// changes from sync to them. +// Lives entirely on the FILE thread. +class ExtensionSettings : public SyncableService { public: // File path is the base of the extension settings directory. // The databases will be at base_path/extension_id. explicit ExtensionSettings(const FilePath& base_path); + virtual ~ExtensionSettings(); + // Gets a weak reference to the storage area for a given extension. // Must be run on the FILE thread. // // By default this will be of a cached LEVELDB storage, but on failure to // create a leveldb instance will fall back to cached NOOP storage. - ExtensionSettingsStorage* GetStorage(const std::string& extension_id); + ExtensionSettingsStorage* GetStorage( + const std::string& extension_id) const; // Gets a weak reference to the storage area for a given extension, with a // specific type and whether it should be wrapped in a cache. @@ -32,25 +41,60 @@ class ExtensionSettings : public base::RefCountedThreadSafe<ExtensionSettings> { ExtensionSettingsStorage* GetStorageForTesting( ExtensionSettingsStorage::Type type, bool cached, - const std::string& extension_id); + const std::string& extension_id) const; + + // SyncableService implementation. + virtual SyncDataList GetAllSyncData(syncable::ModelType type) const OVERRIDE; + virtual SyncError MergeDataAndStartSyncing( + syncable::ModelType type, + const SyncDataList& initial_sync_data, + SyncChangeProcessor* sync_processor) OVERRIDE; + virtual SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const SyncChangeList& change_list) OVERRIDE; + virtual void StopSyncing(syncable::ModelType type) OVERRIDE; private: - friend class base::RefCountedThreadSafe<ExtensionSettings>; - virtual ~ExtensionSettings(); + // Gets a weak reference to the storage area for a given extension, + // initializing sync with some initial data if sync enabled. + // + // By default this will be of a cached LEVELDB storage, but on failure to + // create a leveldb instance will fall back to cached NOOP storage. + SyncableExtensionSettingsStorage* GetOrCreateStorageWithSyncData( + const std::string& extension_id, const DictionaryValue& sync_data) const; - // Creates a storage area of a given type, optionally wrapped in a cache. - // Returns NULL if creation fails. - ExtensionSettingsStorage* CreateStorage( + // If a storage area exists in the cache, returns the cached storage area. + // Otherwise tries to create one using CreateAndInitStorage. + SyncableExtensionSettingsStorage* GetOrCreateAndInitStorage( + ExtensionSettingsStorage::Type type, + bool cached, const std::string& extension_id, + const DictionaryValue& initial_sync_data) const; + + // Creates a storage area of a given type, optionally wrapped in a cache. + // Returns NULL if creation fails. Otherwise, adds the new storage area to + // the cache and initializes sync if sync is enabled. + SyncableExtensionSettingsStorage* CreateAndInitStorage( ExtensionSettingsStorage::Type type, - bool cached); + bool cached, + const std::string& extension_id, + const DictionaryValue& initial_sync_data) const; + + // Gets all extension IDs known to extension settings. This may not be all + // installed extensions. + std::set<std::string> GetKnownExtensionIDs() const; // The base file path to create any leveldb databases at. const FilePath base_path_; // A cache of ExtensionSettingsStorage objects that have already been created. // Ensure that there is only ever one created per extension. - std::map<std::string, ExtensionSettingsStorage*> storage_objs_; + typedef std::map<std::string, linked_ptr<SyncableExtensionSettingsStorage> > + StorageObjMap; + mutable StorageObjMap storage_objs_; + + // Current sync processor, if any. + SyncChangeProcessor* sync_processor_; DISALLOW_COPY_AND_ASSIGN(ExtensionSettings); }; diff --git a/chrome/browser/extensions/extension_settings_api.cc b/chrome/browser/extensions/extension_settings_api.cc index 7167901..ad90690 100644 --- a/chrome/browser/extensions/extension_settings_api.cc +++ b/chrome/browser/extensions/extension_settings_api.cc @@ -16,17 +16,14 @@ const char* kUnsupportedArgumentType = "Unsupported argument type"; // SettingsFunction bool SettingsFunction::RunImpl() { - BrowserThread::PostTask( - BrowserThread::FILE, - FROM_HERE, - base::Bind(&SettingsFunction::RunOnFileThread, this)); + profile()->GetExtensionService()->extension_settings()->RunWithSettings( + base::Bind(&SettingsFunction::RunWithSettingsOnFileThread, this)); return true; } -void SettingsFunction::RunOnFileThread() { +void SettingsFunction::RunWithSettingsOnFileThread( + ExtensionSettings* settings) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - ExtensionSettings* settings = - profile()->GetExtensionService()->extension_settings(); bool success = RunWithStorage(settings->GetStorage(extension_id())); BrowserThread::PostTask( BrowserThread::UI, diff --git a/chrome/browser/extensions/extension_settings_api.h b/chrome/browser/extensions/extension_settings_api.h index 1ca2180..2cc87a0 100644 --- a/chrome/browser/extensions/extension_settings_api.h +++ b/chrome/browser/extensions/extension_settings_api.h @@ -28,8 +28,9 @@ class SettingsFunction : public AsyncExtensionFunction { bool UseResult(const ExtensionSettingsStorage::Result& storage_result); private: - // Component of RunImpl which runs on the FILE thread. - void RunOnFileThread(); + // Called via PostTask from RunImpl. Calls RunWithStorage and then + // SendReponse with its success value. + void RunWithSettingsOnFileThread(ExtensionSettings* settings); }; class GetSettingsFunction : public SettingsFunction { diff --git a/chrome/browser/extensions/extension_settings_cached_leveldb_storage_unittest.cc b/chrome/browser/extensions/extension_settings_cached_leveldb_storage_unittest.cc index 765081a..6ae82fc 100644 --- a/chrome/browser/extensions/extension_settings_cached_leveldb_storage_unittest.cc +++ b/chrome/browser/extensions/extension_settings_cached_leveldb_storage_unittest.cc @@ -7,8 +7,8 @@ namespace { ExtensionSettingsStorage* Param( - ExtensionSettings* settings, const std::string& extension_id) { - return settings->GetStorageForTesting( + const ExtensionSettings& settings, const std::string& extension_id) { + return settings.GetStorageForTesting( ExtensionSettingsStorage::LEVELDB, true, extension_id); } diff --git a/chrome/browser/extensions/extension_settings_cached_noop_storage_unittest.cc b/chrome/browser/extensions/extension_settings_cached_noop_storage_unittest.cc index 252b05d..8178e82 100644 --- a/chrome/browser/extensions/extension_settings_cached_noop_storage_unittest.cc +++ b/chrome/browser/extensions/extension_settings_cached_noop_storage_unittest.cc @@ -7,8 +7,8 @@ namespace { ExtensionSettingsStorage* Param( - ExtensionSettings* settings, const std::string& extension_id) { - return settings->GetStorageForTesting( + const ExtensionSettings& settings, const std::string& extension_id) { + return settings.GetStorageForTesting( ExtensionSettingsStorage::NOOP, true, extension_id); } diff --git a/chrome/browser/extensions/extension_settings_leveldb_storage_unittest.cc b/chrome/browser/extensions/extension_settings_leveldb_storage_unittest.cc index 02dc3bd..f6fae37 100644 --- a/chrome/browser/extensions/extension_settings_leveldb_storage_unittest.cc +++ b/chrome/browser/extensions/extension_settings_leveldb_storage_unittest.cc @@ -7,9 +7,8 @@ namespace { ExtensionSettingsStorage* Param( - ExtensionSettings* settings, - const std::string& extension_id) { - return settings->GetStorageForTesting( + const ExtensionSettings& settings, const std::string& extension_id) { + return settings.GetStorageForTesting( ExtensionSettingsStorage::LEVELDB, false, extension_id); } diff --git a/chrome/browser/extensions/extension_settings_storage.h b/chrome/browser/extensions/extension_settings_storage.h index 5918ab2..1a5311fe 100644 --- a/chrome/browser/extensions/extension_settings_storage.h +++ b/chrome/browser/extensions/extension_settings_storage.h @@ -35,7 +35,7 @@ class ExtensionSettingsStorage { // The dictionary result of the computation. NULL does not imply invalid; // HasError() should be used to test this. - // Wwnership remains with with the Result. + // Ownership remains with with the Result. DictionaryValue* GetSettings() const; // Whether there was an error in the computation. If so, the results of diff --git a/chrome/browser/extensions/extension_settings_storage_unittest.cc b/chrome/browser/extensions/extension_settings_storage_unittest.cc index fc1a361..80d6461 100644 --- a/chrome/browser/extensions/extension_settings_storage_unittest.cc +++ b/chrome/browser/extensions/extension_settings_storage_unittest.cc @@ -51,7 +51,9 @@ ExtensionSettingsStorageTest::ExtensionSettingsStorageTest() empty_dict_(new DictionaryValue), dict1_(new DictionaryValue), dict12_(new DictionaryValue), - dict123_(new DictionaryValue) { + dict123_(new DictionaryValue), + ui_thread_(BrowserThread::UI, MessageLoop::current()), + file_thread_(BrowserThread::FILE, MessageLoop::current()) { val1_.reset(Value::CreateStringValue(key1_ + "Value")); val2_.reset(Value::CreateStringValue(key2_ + "Value")); val3_.reset(Value::CreateStringValue(key3_ + "Value")); @@ -77,17 +79,16 @@ ExtensionSettingsStorageTest::ExtensionSettingsStorageTest() ExtensionSettingsStorageTest::~ExtensionSettingsStorageTest() {} void ExtensionSettingsStorageTest::SetUp() { - ui_message_loop_.reset(new MessageLoopForUI()); - ui_thread_.reset( - new BrowserThread(BrowserThread::UI, MessageLoop::current())); - file_thread_.reset( - new BrowserThread(BrowserThread::FILE, MessageLoop::current())); - - FilePath temp_dir; - file_util::CreateNewTempDirectory(FilePath::StringType(), &temp_dir); - settings_ = new ExtensionSettings(temp_dir); - storage_ = (GetParam())(settings_.get(), "fakeExtension"); - DCHECK(storage_ != NULL); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + settings_.reset(new ExtensionSettings(temp_dir_.path())); + storage_ = (GetParam())(*settings_, "fakeExtension"); + ASSERT_TRUE(storage_ != NULL); +} + +void ExtensionSettingsStorageTest::TearDown() { + // Must do this explicitly here so that it's destroyed before the + // message loops are. + settings_.reset(); } TEST_P(ExtensionSettingsStorageTest, GetWhenEmpty) { diff --git a/chrome/browser/extensions/extension_settings_storage_unittest.h b/chrome/browser/extensions/extension_settings_storage_unittest.h index 1d5d666..9d209a1 100644 --- a/chrome/browser/extensions/extension_settings_storage_unittest.h +++ b/chrome/browser/extensions/extension_settings_storage_unittest.h @@ -11,13 +11,14 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" +#include "base/scoped_temp_dir.h" #include "base/task.h" #include "chrome/browser/extensions/extension_settings.h" #include "content/browser/browser_thread.h" // Parameter type for the value-parameterized tests. typedef ExtensionSettingsStorage* (*ExtensionSettingsStorageTestParam)( - ExtensionSettings* settings, const std::string& extension_id); + const ExtensionSettings& settings, const std::string& extension_id); // Test fixture for ExtensionSettingsStorage tests. Tests are defined in // extension_settings_storage_unittest.cc with configurations for both cached @@ -29,6 +30,7 @@ class ExtensionSettingsStorageTest virtual ~ExtensionSettingsStorageTest(); virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; protected: ExtensionSettingsStorage* storage_; @@ -54,12 +56,13 @@ class ExtensionSettingsStorageTest scoped_ptr<DictionaryValue> dict123_; private: - scoped_refptr<ExtensionSettings> settings_; + ScopedTempDir temp_dir_; + scoped_ptr<ExtensionSettings> settings_; // Need these so that the DCHECKs for running on FILE or UI threads pass. - scoped_ptr<MessageLoopForUI> ui_message_loop_; - scoped_ptr<BrowserThread> ui_thread_; - scoped_ptr<BrowserThread> file_thread_; + MessageLoop message_loop_; + BrowserThread ui_thread_; + BrowserThread file_thread_; }; #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_STORAGE_UNITTEST_H_ diff --git a/chrome/browser/extensions/extension_settings_sync_unittest.cc b/chrome/browser/extensions/extension_settings_sync_unittest.cc new file mode 100644 index 0000000..7f77b1b --- /dev/null +++ b/chrome/browser/extensions/extension_settings_sync_unittest.cc @@ -0,0 +1,492 @@ +// Copyright (c) 2011 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/bind.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "base/task.h" +#include "chrome/browser/extensions/extension_settings.h" +#include "chrome/browser/extensions/extension_settings_storage_cache.h" +#include "chrome/browser/extensions/extension_settings_noop_storage.h" +#include "chrome/browser/extensions/extension_settings_sync_util.h" +#include "chrome/browser/extensions/syncable_extension_settings_storage.h" +#include "chrome/browser/sync/api/sync_change_processor.h" +#include "content/browser/browser_thread.h" + +// TODO(kalman): Integration tests for sync. + +namespace { + +// Gets the pretty-printed JSON for a value. +static std::string GetJson(const Value& value) { + std::string json; + base::JSONWriter::Write(&value, true, &json); + return json; +} + +// Returns whether two Values are equal. +testing::AssertionResult ValuesEq( + const char* expected_expr, + const char* actual_expr, + const Value* expected, + const Value* actual) { + if (expected == actual) { + return testing::AssertionSuccess(); + } + if (!expected && actual) { + return testing::AssertionFailure() << + "Expected NULL, actual: " << GetJson(*actual); + } + if (expected && !actual) { + return testing::AssertionFailure() << + "Expected: " << GetJson(*expected) << ", actual NULL"; + } + if (!expected->Equals(actual)) { + return testing::AssertionFailure() << + "Expected: " << GetJson(*expected) << ", actual: " << GetJson(*actual); + } + return testing::AssertionSuccess(); +} + +// Returns whether the result of a storage operation is an expected value. +// Logs when different. +testing::AssertionResult SettingsEq( + const char* expected_expr, + const char* actual_expr, + const DictionaryValue* expected, + const ExtensionSettingsStorage::Result actual) { + if (actual.HasError()) { + return testing::AssertionFailure() << + "Expected: " << GetJson(*expected) << + ", actual has error: " << actual.GetError(); + } + return ValuesEq( + expected_expr, actual_expr, expected, actual.GetSettings()); +} + +// SyncChangeProcessor which just records the changes made, accessed after +// being converted to the more useful ExtensionSettingSyncData via changes(). +class MockSyncChangeProcessor : public SyncChangeProcessor { + public: + virtual SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const SyncChangeList& change_list) OVERRIDE { + for (SyncChangeList::const_iterator it = change_list.begin(); + it != change_list.end(); ++it) { + changes_.push_back(ExtensionSettingSyncData(*it)); + } + return SyncError(); + } + + const ExtensionSettingSyncDataList& changes() { return changes_; } + + void ClearChanges() { + changes_.clear(); + } + + // Returns the only change for a given extension setting. If there is not + // exactly 1 change for that key, a test assertion will fail. + ExtensionSettingSyncData GetOnlyChange( + const std::string& extension_id, const std::string& key) { + ExtensionSettingSyncDataList matching_changes; + for (ExtensionSettingSyncDataList::iterator it = changes_.begin(); + it != changes_.end(); ++it) { + if (it->extension_id() == extension_id && it->key() == key) { + matching_changes.push_back(*it); + } + } + if (matching_changes.empty()) { + ADD_FAILURE() << "No matching changes for " << extension_id << "/" << + key << " (out of " << changes_.size() << ")"; + return ExtensionSettingSyncData(SyncChange::ACTION_INVALID, "", "", NULL); + } + if (matching_changes.size() != 1u) { + ADD_FAILURE() << matching_changes.size() << " matching changes for " << + extension_id << "/" << key << " (out of " << changes_.size() << ")"; + } + return matching_changes[0]; + } + + private: + ExtensionSettingSyncDataList changes_; +}; + +} // namespace + +class ExtensionSettingsSyncTest : public testing::Test { + public: + ExtensionSettingsSyncTest() + : ui_thread_(BrowserThread::UI, MessageLoop::current()), + file_thread_(BrowserThread::FILE, MessageLoop::current()) {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + settings_.reset(new ExtensionSettings(temp_dir_.path())); + } + + virtual void TearDown() OVERRIDE { + // Must do this explicitly here so that it's destroyed before the + // message loops are. + settings_.reset(); + } + + protected: + // Creates a new extension storage object and adds a record of the extension + // to the extension service. + SyncableExtensionSettingsStorage* GetStorage( + const std::string& extension_id) { + return static_cast<SyncableExtensionSettingsStorage*>( + settings_->GetStorage(extension_id)); + } + + MockSyncChangeProcessor sync_; + scoped_ptr<ExtensionSettings> settings_; + + // Gets all the sync data from settings_ as a map from extension id to its + // sync data. + std::map<std::string, ExtensionSettingSyncDataList> GetAllSyncData() { + SyncDataList as_list = + settings_->GetAllSyncData(syncable::EXTENSION_SETTINGS); + std::map<std::string, ExtensionSettingSyncDataList> as_map; + for (SyncDataList::iterator it = as_list.begin(); + it != as_list.end(); ++it) { + ExtensionSettingSyncData sync_data(*it); + as_map[sync_data.extension_id()].push_back(sync_data); + } + return as_map; + } + + private: + ScopedTempDir temp_dir_; + + // Need these so that the DCHECKs for running on FILE or UI threads pass. + MessageLoop message_loop_; + BrowserThread ui_thread_; + BrowserThread file_thread_; +}; + +TEST_F(ExtensionSettingsSyncTest, NoDataDoesNotInvokeSync) { + ASSERT_EQ(0u, GetAllSyncData().size()); + + // Have one extension created before sync is set up, the other created after. + GetStorage("s1"); + ASSERT_EQ(0u, GetAllSyncData().size()); + + settings_->MergeDataAndStartSyncing( + syncable::EXTENSION_SETTINGS, + SyncDataList(), + &sync_); + + GetStorage("s2"); + ASSERT_EQ(0u, GetAllSyncData().size()); + + settings_->StopSyncing(syncable::EXTENSION_SETTINGS); + + ASSERT_EQ(0u, sync_.changes().size()); + ASSERT_EQ(0u, GetAllSyncData().size()); +} + +TEST_F(ExtensionSettingsSyncTest, InSyncDataDoesNotInvokeSync) { + StringValue value1("fooValue"); + ListValue value2; + value2.Append(StringValue::CreateStringValue("barValue")); + + SyncableExtensionSettingsStorage* storage1 = GetStorage("s1"); + SyncableExtensionSettingsStorage* storage2 = GetStorage("s2"); + + storage1->Set("foo", value1); + storage2->Set("bar", value2); + + std::map<std::string, ExtensionSettingSyncDataList> all_sync_data = + GetAllSyncData(); + ASSERT_EQ(2u, all_sync_data.size()); + ASSERT_EQ(1u, all_sync_data["s1"].size()); + ASSERT_PRED_FORMAT2(ValuesEq, &value1, &all_sync_data["s1"][0].value()); + ASSERT_EQ(1u, all_sync_data["s2"].size()); + ASSERT_PRED_FORMAT2(ValuesEq, &value2, &all_sync_data["s2"][0].value()); + + SyncDataList sync_data; + sync_data.push_back(extension_settings_sync_util::CreateData( + "s1", "foo", value1)); + sync_data.push_back(extension_settings_sync_util::CreateData( + "s2", "bar", value2)); + + settings_->MergeDataAndStartSyncing( + syncable::EXTENSION_SETTINGS, sync_data, &sync_); + settings_->StopSyncing(syncable::EXTENSION_SETTINGS); + + // Already in sync, so no changes. + ASSERT_EQ(0u, sync_.changes().size()); +} + +TEST_F(ExtensionSettingsSyncTest, LocalDataWithNoSyncDataIsPushedToSync) { + StringValue value1("fooValue"); + ListValue value2; + value2.Append(StringValue::CreateStringValue("barValue")); + + SyncableExtensionSettingsStorage* storage1 = GetStorage("s1"); + SyncableExtensionSettingsStorage* storage2 = GetStorage("s2"); + + storage1->Set("foo", value1); + storage2->Set("bar", value2); + + settings_->MergeDataAndStartSyncing( + syncable::EXTENSION_SETTINGS, SyncDataList(), &sync_); + + // All settings should have been pushed to sync. + ASSERT_EQ(2u, sync_.changes().size()); + ExtensionSettingSyncData change = sync_.GetOnlyChange("s1", "foo"); + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type()); + ASSERT_TRUE(value1.Equals(&change.value())); + change = sync_.GetOnlyChange("s2", "bar"); + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type()); + ASSERT_TRUE(value2.Equals(&change.value())); + + settings_->StopSyncing(syncable::EXTENSION_SETTINGS); +} + +TEST_F(ExtensionSettingsSyncTest, AnySyncDataOverwritesLocalData) { + StringValue value1("fooValue"); + ListValue value2; + value2.Append(StringValue::CreateStringValue("barValue")); + + // Maintain dictionaries mirrored to the expected values of the settings in + // each storage area. + DictionaryValue expected1, expected2; + + // Pre-populate one of the storage areas. + SyncableExtensionSettingsStorage* storage1 = GetStorage("s1"); + storage1->Set("overwriteMe", value1); + + SyncDataList sync_data; + sync_data.push_back(extension_settings_sync_util::CreateData( + "s1", "foo", value1)); + sync_data.push_back(extension_settings_sync_util::CreateData( + "s2", "bar", value2)); + settings_->MergeDataAndStartSyncing( + syncable::EXTENSION_SETTINGS, sync_data, &sync_); + expected1.Set("foo", value1.DeepCopy()); + expected2.Set("bar", value2.DeepCopy()); + + SyncableExtensionSettingsStorage* storage2 = GetStorage("s2"); + + // All changes should be local, so no sync changes. + ASSERT_EQ(0u, sync_.changes().size()); + + // Sync settings should have been pushed to local settings. + ASSERT_PRED_FORMAT2(SettingsEq, &expected1, storage1->Get()); + ASSERT_PRED_FORMAT2(SettingsEq, &expected2, storage2->Get()); + + settings_->StopSyncing(syncable::EXTENSION_SETTINGS); +} + +TEST_F(ExtensionSettingsSyncTest, ProcessSyncChanges) { + StringValue value1("fooValue"); + ListValue value2; + value2.Append(StringValue::CreateStringValue("barValue")); + + // Maintain dictionaries mirrored to the expected values of the settings in + // each storage area. + DictionaryValue expected1, expected2; + + // Make storage1 initialised from local data, storage2 initialised from sync. + SyncableExtensionSettingsStorage* storage1 = GetStorage("s1"); + SyncableExtensionSettingsStorage* storage2 = GetStorage("s2"); + + storage1->Set("foo", value1); + expected1.Set("foo", value1.DeepCopy()); + + SyncDataList sync_data; + sync_data.push_back(extension_settings_sync_util::CreateData( + "s2", "bar", value2)); + + settings_->MergeDataAndStartSyncing( + syncable::EXTENSION_SETTINGS, sync_data, &sync_); + expected2.Set("bar", value2.DeepCopy()); + + // Make sync add some settings. + SyncChangeList change_list; + change_list.push_back(extension_settings_sync_util::CreateAdd( + "s1", "bar", value2)); + change_list.push_back(extension_settings_sync_util::CreateAdd( + "s2", "foo", value1)); + settings_->ProcessSyncChanges(FROM_HERE, change_list); + expected1.Set("bar", value2.DeepCopy()); + expected2.Set("foo", value1.DeepCopy()); + + ASSERT_PRED_FORMAT2(SettingsEq, &expected1, storage1->Get()); + ASSERT_PRED_FORMAT2(SettingsEq, &expected2, storage2->Get()); + + // Make sync update some settings, storage1 the new setting, storage2 the + // initial setting. + change_list.clear(); + change_list.push_back(extension_settings_sync_util::CreateUpdate( + "s1", "bar", value2)); + change_list.push_back(extension_settings_sync_util::CreateUpdate( + "s2", "bar", value1)); + settings_->ProcessSyncChanges(FROM_HERE, change_list); + expected1.Set("bar", value2.DeepCopy()); + expected2.Set("bar", value1.DeepCopy()); + + ASSERT_PRED_FORMAT2(SettingsEq, &expected1, storage1->Get()); + ASSERT_PRED_FORMAT2(SettingsEq, &expected2, storage2->Get()); + + // Make sync remove some settings, storage1 the initial setting, storage2 the + // new setting. + change_list.clear(); + change_list.push_back(extension_settings_sync_util::CreateDelete( + "s1", "foo")); + change_list.push_back(extension_settings_sync_util::CreateDelete( + "s2", "foo")); + settings_->ProcessSyncChanges(FROM_HERE, change_list); + expected1.Remove("foo", NULL); + expected2.Remove("foo", NULL); + + ASSERT_PRED_FORMAT2(SettingsEq, &expected1, storage1->Get()); + ASSERT_PRED_FORMAT2(SettingsEq, &expected2, storage2->Get()); + + settings_->StopSyncing(syncable::EXTENSION_SETTINGS); +} + +TEST_F(ExtensionSettingsSyncTest, PushToSync) { + StringValue value1("fooValue"); + ListValue value2; + value2.Append(StringValue::CreateStringValue("barValue")); + + // Make storage1/2 initialised from local data, storage3/4 initialised from + // sync. + SyncableExtensionSettingsStorage* storage1 = GetStorage("s1"); + SyncableExtensionSettingsStorage* storage2 = GetStorage("s2"); + SyncableExtensionSettingsStorage* storage3 = GetStorage("s3"); + SyncableExtensionSettingsStorage* storage4 = GetStorage("s4"); + + storage1->Set("foo", value1); + storage2->Set("foo", value1); + + SyncDataList sync_data; + sync_data.push_back(extension_settings_sync_util::CreateData( + "s3", "bar", value2)); + sync_data.push_back(extension_settings_sync_util::CreateData( + "s4", "bar", value2)); + + settings_->MergeDataAndStartSyncing( + syncable::EXTENSION_SETTINGS, sync_data, &sync_); + + // Add something locally. + storage1->Set("bar", value2); + storage2->Set("bar", value2); + storage3->Set("foo", value1); + storage4->Set("foo", value1); + + ExtensionSettingSyncData change = sync_.GetOnlyChange("s1", "bar"); + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type()); + ASSERT_TRUE(value2.Equals(&change.value())); + sync_.GetOnlyChange("s2", "bar"); + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type()); + ASSERT_TRUE(value2.Equals(&change.value())); + change = sync_.GetOnlyChange("s3", "foo"); + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type()); + ASSERT_TRUE(value1.Equals(&change.value())); + change = sync_.GetOnlyChange("s4", "foo"); + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type()); + ASSERT_TRUE(value1.Equals(&change.value())); + + // Change something locally, storage1/3 the new setting and storage2/4 the + // initial setting, for all combinations of local vs sync intialisation and + // new vs initial. + sync_.ClearChanges(); + storage1->Set("bar", value1); + storage2->Set("foo", value2); + storage3->Set("bar", value1); + storage4->Set("foo", value2); + + change = sync_.GetOnlyChange("s1", "bar"); + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type()); + ASSERT_TRUE(value1.Equals(&change.value())); + change = sync_.GetOnlyChange("s2", "foo"); + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type()); + ASSERT_TRUE(value2.Equals(&change.value())); + change = sync_.GetOnlyChange("s3", "bar"); + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type()); + ASSERT_TRUE(value1.Equals(&change.value())); + change = sync_.GetOnlyChange("s4", "foo"); + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type()); + ASSERT_TRUE(value2.Equals(&change.value())); + + // Remove something locally, storage1/3 the new setting and storage2/4 the + // initial setting, for all combinations of local vs sync intialisation and + // new vs initial. + sync_.ClearChanges(); + storage1->Remove("foo"); + storage2->Remove("bar"); + storage3->Remove("foo"); + storage4->Remove("bar"); + + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s1", "foo").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s2", "bar").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s3", "foo").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s4", "bar").change_type()); + + // Remove some nonexistent settings. + sync_.ClearChanges(); + storage1->Remove("foo"); + storage2->Remove("bar"); + storage3->Remove("foo"); + storage4->Remove("bar"); + + ASSERT_EQ(0u, sync_.changes().size()); + + // Clear the rest of the settings. Add the removed ones back first so that + // more than one setting is cleared. + storage1->Set("foo", value1); + storage2->Set("bar", value2); + storage3->Set("foo", value1); + storage4->Set("bar", value2); + + sync_.ClearChanges(); + storage1->Clear(); + storage2->Clear(); + storage3->Clear(); + storage4->Clear(); + + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s1", "foo").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s1", "bar").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s2", "foo").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s2", "bar").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s3", "foo").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s3", "bar").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s4", "foo").change_type()); + ASSERT_EQ( + SyncChange::ACTION_DELETE, + sync_.GetOnlyChange("s4", "bar").change_type()); + + settings_->StopSyncing(syncable::EXTENSION_SETTINGS); +} diff --git a/chrome/browser/extensions/extension_settings_sync_util.cc b/chrome/browser/extensions/extension_settings_sync_util.cc new file mode 100644 index 0000000..d41a3e4 --- /dev/null +++ b/chrome/browser/extensions/extension_settings_sync_util.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2011 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/extensions/extension_settings_sync_util.h" + +#include "base/values.h" +#include "base/json/json_writer.h" +#include "chrome/browser/sync/protocol/extension_setting_specifics.pb.h" + +namespace extension_settings_sync_util { + +SyncData CreateData( + const std::string& extension_id, + const std::string& key, + const Value& value) { + sync_pb::EntitySpecifics specifics; + sync_pb::ExtensionSettingSpecifics* setting_specifics = + specifics.MutableExtension(sync_pb::extension_setting); + setting_specifics->set_extension_id(extension_id); + setting_specifics->set_key(key); + std::string value_as_json; + base::JSONWriter::Write(&value, false, &value_as_json); + setting_specifics->set_value(value_as_json); + return SyncData::CreateLocalData(extension_id + "/" + key, key, specifics); +} + +SyncChange CreateAdd( + const std::string& extension_id, + const std::string& key, + const Value& value) { + return SyncChange( + SyncChange::ACTION_ADD, CreateData(extension_id, key, value)); +} + +SyncChange CreateUpdate( + const std::string& extension_id, + const std::string& key, + const Value& value) { + return SyncChange( + SyncChange::ACTION_UPDATE, CreateData(extension_id, key, value)); +} + +SyncChange CreateDelete( + const std::string& extension_id, const std::string& key) { + DictionaryValue no_value; + return SyncChange( + SyncChange::ACTION_DELETE, CreateData(extension_id, key, no_value)); +} + +} // namespace extension_settings_sync_util diff --git a/chrome/browser/extensions/extension_settings_sync_util.h b/chrome/browser/extensions/extension_settings_sync_util.h new file mode 100644 index 0000000..c2107d5 --- /dev/null +++ b/chrome/browser/extensions/extension_settings_sync_util.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 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_EXTENSIONS_EXTENSION_SETTINGS_SYNC_UTIL_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_SYNC_UTIL_H_ +#pragma once + + +#include "chrome/browser/sync/api/sync_data.h" +#include "chrome/browser/sync/api/sync_change.h" + +namespace base { +class Value; +} // namespace base + +namespace extension_settings_sync_util { + +// Creates a SyncData object for an extension setting. +SyncData CreateData( + const std::string& extension_id, + const std::string& key, + const base::Value& value); + +// Creates an "add" sync change for an extension setting. +SyncChange CreateAdd( + const std::string& extension_id, + const std::string& key, + const base::Value& value); + +// Creates an "update" sync change for an extension setting. +SyncChange CreateUpdate( + const std::string& extension_id, + const std::string& key, + const base::Value& value); + +// Creates a "delete" sync change for an extension setting. +SyncChange CreateDelete( + const std::string& extension_id, const std::string& key); + +} // namespace extension_settings_sync_util + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_SYNC_UTIL_H_ diff --git a/chrome/browser/extensions/extension_settings_ui_wrapper.cc b/chrome/browser/extensions/extension_settings_ui_wrapper.cc new file mode 100644 index 0000000..20cad1e --- /dev/null +++ b/chrome/browser/extensions/extension_settings_ui_wrapper.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2011 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/extensions/extension_settings_ui_wrapper.h" + +#include "base/bind.h" +#include "base/file_path.h" +#include "chrome/browser/extensions/extension_settings.h" +#include "content/browser/browser_thread.h" + +ExtensionSettingsUIWrapper::ExtensionSettingsUIWrapper( + const FilePath& base_path) + : core_(new ExtensionSettingsUIWrapper::Core()) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserThread::PostTask( + BrowserThread::FILE, + FROM_HERE, + base::Bind( + &ExtensionSettingsUIWrapper::Core::InitOnFileThread, + core_.get(), + base_path)); +} + +void ExtensionSettingsUIWrapper::RunWithSettings( + const SettingsCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserThread::PostTask( + BrowserThread::FILE, + FROM_HERE, + base::Bind( + &ExtensionSettingsUIWrapper::Core::RunWithSettingsOnFileThread, + core_.get(), + callback)); +} + +ExtensionSettingsUIWrapper::~ExtensionSettingsUIWrapper() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +ExtensionSettingsUIWrapper::Core::Core() + : extension_settings_(NULL) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +void ExtensionSettingsUIWrapper::Core::InitOnFileThread( + const FilePath& base_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(!extension_settings_); + extension_settings_ = new ExtensionSettings(base_path); +} + +void ExtensionSettingsUIWrapper::Core::RunWithSettingsOnFileThread( + const SettingsCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(extension_settings_); + callback.Run(extension_settings_); +} + +ExtensionSettingsUIWrapper::Core::~Core() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || + BrowserThread::CurrentlyOn(BrowserThread::FILE)); + BrowserThread::DeleteSoon( + BrowserThread::FILE, FROM_HERE, extension_settings_); +} diff --git a/chrome/browser/extensions/extension_settings_ui_wrapper.h b/chrome/browser/extensions/extension_settings_ui_wrapper.h new file mode 100644 index 0000000..f5cf1fd --- /dev/null +++ b/chrome/browser/extensions/extension_settings_ui_wrapper.h @@ -0,0 +1,63 @@ +// Copyright (c) 2011 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_EXTENSIONS_EXTENSION_SETTINGS_UI_WRAPPER_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_UI_WRAPPER_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/callback.h" +#include "chrome/browser/sync/api/syncable_service.h" + +class FilePath; +class ExtensionSettings; +class ExtensionSettingsStorage; + +// Wrapper for an ExtensionSettings object for dealing with thread ownership. +// This class lives on the UI thread while ExtensionSettings object live on +// the FILE thread. +class ExtensionSettingsUIWrapper { + public: + explicit ExtensionSettingsUIWrapper(const FilePath& base_path); + + typedef base::Callback<void(ExtensionSettings*)> SettingsCallback; + + // Runs |callback| on the FILE thread with the extension settings. + void RunWithSettings(const SettingsCallback& callback); + + ~ExtensionSettingsUIWrapper(); + + private: + // Ref-counted container for the ExtensionSettings object. + class Core : public base::RefCountedThreadSafe<Core> { + public: + // Constructed on UI thread. + Core(); + + // Does any FILE thread specific initialization, such as + // construction of |extension_settings_|. Must be called before + // any call to RunWithSettingsOnFileThread(). + void InitOnFileThread(const FilePath& base_path); + + // Runs |callback| with extension settings. + void RunWithSettingsOnFileThread(const SettingsCallback& callback); + + private: + // Can be destroyed on either the UI or FILE thread. + virtual ~Core(); + friend class base::RefCountedThreadSafe<Core>; + + // Lives on the FILE thread. + ExtensionSettings* extension_settings_; + + DISALLOW_COPY_AND_ASSIGN(Core); + }; + + scoped_refptr<Core> core_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionSettingsUIWrapper); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_UI_WRAPPER_H_ diff --git a/chrome/browser/extensions/syncable_extension_settings_storage.cc b/chrome/browser/extensions/syncable_extension_settings_storage.cc new file mode 100644 index 0000000..137a6cb --- /dev/null +++ b/chrome/browser/extensions/syncable_extension_settings_storage.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2011 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/extensions/syncable_extension_settings_storage.h" + +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/extensions/extension_settings_sync_util.h" +#include "chrome/browser/sync/api/sync_data.h" +#include "chrome/browser/sync/protocol/extension_setting_specifics.pb.h" +#include "content/browser/browser_thread.h" + +namespace { + +// Inserts all the keys from a dictionary into a set of strings. +void InsertAll(const DictionaryValue& src, std::set<std::string>* dst) { + for (DictionaryValue::key_iterator it = src.begin_keys(); + it != src.end_keys(); ++it) { + dst->insert(*it); + } +} + +} // namespace + +SyncableExtensionSettingsStorage::SyncableExtensionSettingsStorage( + std::string extension_id, ExtensionSettingsStorage* delegate) + : extension_id_(extension_id), delegate_(delegate), sync_processor_(NULL) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); +} + +SyncableExtensionSettingsStorage::~SyncableExtensionSettingsStorage() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Get( + const std::string& key) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + return delegate_->Get(key); +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Get( + const std::vector<std::string>& keys) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + return delegate_->Get(keys); +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Get() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + return delegate_->Get(); +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Set( + const std::string& key, const Value& value) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Result result = delegate_->Set(key, value); + if (result.HasError()) { + return result; + } + if (sync_processor_) { + SendAddsOrUpdatesToSync(*result.GetSettings()); + } + return result; +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Set( + const DictionaryValue& values) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Result result = delegate_->Set(values); + if (result.HasError()) { + return result; + } + if (sync_processor_) { + SendAddsOrUpdatesToSync(*result.GetSettings()); + } + return result; +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Remove( + const std::string& key) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Result result = delegate_->Remove(key); + if (result.HasError()) { + return result; + } + if (sync_processor_) { + std::vector<std::string> keys; + keys.push_back(key); + SendDeletesToSync(keys); + } + return result; +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Remove( + const std::vector<std::string>& keys) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Result result = delegate_->Remove(keys); + if (result.HasError()) { + return result; + } + if (sync_processor_) { + SendDeletesToSync(keys); + } + return result; +} + +ExtensionSettingsStorage::Result SyncableExtensionSettingsStorage::Clear() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Result result = delegate_->Clear(); + if (result.HasError()) { + return result; + } + if (sync_processor_) { + SendDeletesToSync( + std::vector<std::string>(synced_keys_.begin(), synced_keys_.end())); + } + return result; +} + +// Sync-related methods. + +SyncError SyncableExtensionSettingsStorage::StartSyncing( + const DictionaryValue& sync_state, SyncChangeProcessor* sync_processor) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(!sync_processor_); + DCHECK(synced_keys_.empty()); + sync_processor_ = sync_processor; + InsertAll(sync_state, &synced_keys_); + + Result maybe_settings = delegate_->Get(); + if (maybe_settings.HasError()) { + return SyncError( + FROM_HERE, + std::string("Failed to get settings: ") + maybe_settings.GetError(), + syncable::EXTENSION_SETTINGS); + } + + DictionaryValue* settings = maybe_settings.GetSettings(); + if (sync_state.empty()) { + return SendLocalSettingsToSync(*settings); + } + return OverwriteLocalSettingsWithSync(sync_state, *settings); +} + +SyncError SyncableExtensionSettingsStorage::SendLocalSettingsToSync( + const DictionaryValue& settings) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + SyncChangeList changes; + for (DictionaryValue::key_iterator it = settings.begin_keys(); + it != settings.end_keys(); ++it) { + Value* value; + settings.GetWithoutPathExpansion(*it, &value); + changes.push_back( + extension_settings_sync_util::CreateAdd(extension_id_, *it, *value)); + } + + if (changes.empty()) { + return SyncError(); + } + + SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, changes); + if (error.IsSet()) { + return error; + } + + InsertAll(settings, &synced_keys_); + return SyncError(); +} + +SyncError SyncableExtensionSettingsStorage::OverwriteLocalSettingsWithSync( + const DictionaryValue& sync_state, const DictionaryValue& settings) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + // Treat this as a list of changes to sync and use ProcessSyncChanges. + // This gives notifications etc for free. + scoped_ptr<DictionaryValue> new_sync_state(sync_state.DeepCopy()); + + ExtensionSettingSyncDataList changes; + for (DictionaryValue::key_iterator it = settings.begin_keys(); + it != settings.end_keys(); ++it) { + Value* sync_value = NULL; + if (new_sync_state->RemoveWithoutPathExpansion(*it, &sync_value)) { + Value* local_value = NULL; + settings.GetWithoutPathExpansion(*it, &local_value); + if (!local_value->Equals(sync_value)) { + // Sync value is different, update local setting with new value. + changes.push_back( + ExtensionSettingSyncData( + SyncChange::ACTION_UPDATE, extension_id_, *it, sync_value)); + } + } else { + // Not synced, delete local setting. + changes.push_back( + ExtensionSettingSyncData( + SyncChange::ACTION_DELETE, + extension_id_, + *it, + new DictionaryValue())); + } + } + + // Add all new settings to local settings. + while (!new_sync_state->empty()) { + std::string key = *new_sync_state->begin_keys(); + Value* value; + new_sync_state->RemoveWithoutPathExpansion(key, &value); + changes.push_back( + ExtensionSettingSyncData( + SyncChange::ACTION_ADD, extension_id_, key, value)); + } + + if (changes.empty()) { + return SyncError(); + } + + std::vector<SyncError> sync_errors(ProcessSyncChanges(changes)); + if (sync_errors.empty()) { + return SyncError(); + } + // TODO(kalman): something sensible with multiple errors. + return sync_errors[0]; +} + +void SyncableExtensionSettingsStorage::StopSyncing() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(sync_processor_); + sync_processor_ = NULL; + synced_keys_.clear(); +} + +std::vector<SyncError> SyncableExtensionSettingsStorage::ProcessSyncChanges( + const ExtensionSettingSyncDataList& sync_changes) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(sync_processor_); + DCHECK(!sync_changes.empty()) << "No sync changes for " << extension_id_; + + std::vector<SyncError> errors; + + for (ExtensionSettingSyncDataList::const_iterator it = sync_changes.begin(); + it != sync_changes.end(); ++it) { + DCHECK_EQ(extension_id_, it->extension_id()); + switch (it->change_type()) { + case SyncChange::ACTION_ADD: + case SyncChange::ACTION_UPDATE: { + synced_keys_.insert(it->key()); + Result result = delegate_->Set(it->key(), it->value()); + if (result.HasError()) { + errors.push_back(SyncError( + FROM_HERE, + std::string("Error pushing sync change to local settings: ") + + result.GetError(), + syncable::EXTENSION_SETTINGS)); + } + break; + } + + case SyncChange::ACTION_DELETE: { + synced_keys_.erase(it->key()); + Result result = delegate_->Remove(it->key()); + if (result.HasError()) { + errors.push_back(SyncError( + FROM_HERE, + std::string("Error removing sync change from local settings: ") + + result.GetError(), + syncable::EXTENSION_SETTINGS)); + } + break; + } + + default: + NOTREACHED(); + } + } + + return errors; +} + +void SyncableExtensionSettingsStorage::SendAddsOrUpdatesToSync( + const DictionaryValue& settings) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(sync_processor_); + + SyncChangeList changes; + for (DictionaryValue::key_iterator it = settings.begin_keys(); + it != settings.end_keys(); ++it) { + Value* value = NULL; + settings.GetWithoutPathExpansion(*it, &value); + DCHECK(value); + if (synced_keys_.count(*it)) { + // Key is synced, send ACTION_UPDATE to sync. + changes.push_back( + extension_settings_sync_util::CreateUpdate( + extension_id_, *it, *value)); + } else { + // Key is not synced, send ACTION_ADD to sync. + changes.push_back( + extension_settings_sync_util::CreateAdd(extension_id_, *it, *value)); + } + } + + if (changes.empty()) { + return; + } + + SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, changes); + if (error.IsSet()) { + LOG(WARNING) << "Failed to send changes to sync: " << error.message(); + return; + } + + InsertAll(settings, &synced_keys_); +} + +void SyncableExtensionSettingsStorage::SendDeletesToSync( + const std::vector<std::string>& keys) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(sync_processor_); + + SyncChangeList changes; + for (std::vector<std::string>::const_iterator it = keys.begin(); + it != keys.end(); ++it) { + // Only remove from sync if it's actually synced. + if (synced_keys_.count(*it)) { + changes.push_back( + extension_settings_sync_util::CreateDelete(extension_id_, *it)); + } + } + + if (changes.empty()) { + return; + } + + SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, changes); + if (error.IsSet()) { + LOG(WARNING) << "Failed to send changes to sync: " << error.message(); + return; + } + + for (std::vector<std::string>::const_iterator it = keys.begin(); + it != keys.end(); ++it) { + synced_keys_.erase(*it); + } +} diff --git a/chrome/browser/extensions/syncable_extension_settings_storage.h b/chrome/browser/extensions/syncable_extension_settings_storage.h new file mode 100644 index 0000000..3232eb4 --- /dev/null +++ b/chrome/browser/extensions/syncable_extension_settings_storage.h @@ -0,0 +1,79 @@ +// Copyright (c) 2011 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_EXTENSIONS_SYNCABLE_EXTENSION_SETTINGS_STORAGE_H_ +#define CHROME_BROWSER_EXTENSIONS_SYNCABLE_EXTENSION_SETTINGS_STORAGE_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "chrome/browser/extensions/extension_settings_storage.h" +#include "chrome/browser/extensions/extension_setting_sync_data.h" +#include "chrome/browser/sync/api/syncable_service.h" +#include "chrome/browser/sync/api/sync_change.h" + +// Decorates an ExtensionSettingsStorage with sync behaviour. +class SyncableExtensionSettingsStorage : public ExtensionSettingsStorage { + public: + explicit SyncableExtensionSettingsStorage( + std::string extension_id, + // Ownership taken. + ExtensionSettingsStorage* delegate); + + virtual ~SyncableExtensionSettingsStorage(); + + // ExtensionSettingsStorage implementation. + virtual Result Get(const std::string& key) OVERRIDE; + virtual Result Get(const std::vector<std::string>& keys) OVERRIDE; + virtual Result Get() OVERRIDE; + virtual Result Set(const std::string& key, const Value& value) OVERRIDE; + virtual Result Set(const DictionaryValue& settings) OVERRIDE; + virtual Result Remove(const std::string& key) OVERRIDE; + virtual Result Remove(const std::vector<std::string>& keys) OVERRIDE; + virtual Result Clear() OVERRIDE; + + // Sync-related methods, analogous to those on SyncableService (handled by + // ExtensionSettings). + SyncError StartSyncing( + const DictionaryValue& sync_state, + // Must NOT be NULL. Ownership NOT taken. + SyncChangeProcessor* sync_processor); + void StopSyncing(); + std::vector<SyncError> ProcessSyncChanges( + const ExtensionSettingSyncDataList& sync_changes); + + private: + // Either adds to sync or send updates to sync for some settings. + // Whether they're adds or updates depends on the state of synced_keys_. + void SendAddsOrUpdatesToSync(const DictionaryValue& settings); + + // Sends deletes to sync for some settings. + void SendDeletesToSync(const std::vector<std::string>& keys); + + // Sends all local settings to sync (synced settings assumed to be empty). + SyncError SendLocalSettingsToSync( + const DictionaryValue& settings); + + // Overwrites local state with sync state. + SyncError OverwriteLocalSettingsWithSync( + const DictionaryValue& sync_state, const DictionaryValue& settings); + + // Id of the extension these settings are for. + std::string const extension_id_; + + // Storage area to sync. + const scoped_ptr<ExtensionSettingsStorage> delegate_; + + // Sync processor. Non-NULL while sync is enabled (between calls to + // StartSyncing and StopSyncing). + SyncChangeProcessor* sync_processor_; + + // Keys of the settings that are currently being synced. + std::set<std::string> synced_keys_; + + DISALLOW_COPY_AND_ASSIGN(SyncableExtensionSettingsStorage); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_SYNCABLE_EXTENSION_SETTINGS_STORAGE_H_ diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index 72fe06b..77deefb 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc @@ -506,7 +506,6 @@ void ProfileImpl::InitExtensions(bool extensions_enabled) { CommandLine::ForCurrentProcess(), GetPath().AppendASCII(ExtensionService::kInstallDirectoryName), extension_prefs_.get(), - extension_settings_.get(), autoupdate_enabled, extensions_enabled)); @@ -983,9 +982,6 @@ void ProfileImpl::OnPrefsLoaded(bool success) { GetPath().AppendASCII(ExtensionService::kInstallDirectoryName), GetExtensionPrefValueMap())); - extension_settings_ = new ExtensionSettings( - GetPath().AppendASCII(ExtensionService::kSettingsDirectoryName)); - ProfileDependencyManager::GetInstance()->CreateProfileServices(this, false); DCHECK(!net_pref_observer_.get()); diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h index b22562e..d588fc1 100644 --- a/chrome/browser/profiles/profile_impl.h +++ b/chrome/browser/profiles/profile_impl.h @@ -198,7 +198,6 @@ class ProfileImpl : public Profile, // Keep extension_prefs_ on top of extension_service_ because the latter // maintains a pointer to the first and shall be destructed first. scoped_ptr<ExtensionPrefs> extension_prefs_; - scoped_refptr<ExtensionSettings> extension_settings_; scoped_ptr<ExtensionService> extension_service_; scoped_refptr<UserScriptMaster> user_script_master_; scoped_refptr<ExtensionDevToolsManager> extension_devtools_manager_; diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc index fb62dea..be4f368 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl.cc @@ -31,6 +31,7 @@ static const syncable::ModelType kStartOrder[] = { syncable::PREFERENCES, syncable::AUTOFILL, syncable::AUTOFILL_PROFILE, + syncable::EXTENSION_SETTINGS, syncable::EXTENSIONS, syncable::APPS, syncable::THEMES, diff --git a/chrome/browser/sync/glue/extension_setting_data_type_controller.cc b/chrome/browser/sync/glue/extension_setting_data_type_controller.cc new file mode 100644 index 0000000..6649ca8 --- /dev/null +++ b/chrome/browser/sync/glue/extension_setting_data_type_controller.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2011 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_setting_data_type_controller.h" + +#include "base/metrics/histogram.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_settings_ui_wrapper.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/api/syncable_service.h" +#include "chrome/browser/sync/glue/generic_change_processor.h" +#include "chrome/browser/sync/profile_sync_factory.h" +#include "content/browser/browser_thread.h" + +namespace browser_sync { + +ExtensionSettingDataTypeController::ExtensionSettingDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* profile_sync_service) + : NonFrontendDataTypeController(profile_sync_factory, profile), + extension_settings_ui_wrapper_( + profile->GetExtensionService()->extension_settings()), + profile_sync_service_(profile_sync_service), + extension_settings_(NULL) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +ExtensionSettingDataTypeController::~ExtensionSettingDataTypeController() {} + +syncable::ModelType ExtensionSettingDataTypeController::type() const { + return syncable::EXTENSION_SETTINGS; +} + +browser_sync::ModelSafeGroup +ExtensionSettingDataTypeController::model_safe_group() const { + return browser_sync::GROUP_FILE; +} + +bool ExtensionSettingDataTypeController::StartModels() { + return true; +} + +bool ExtensionSettingDataTypeController::StartAssociationAsync() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(state(), ASSOCIATING); + extension_settings_ui_wrapper_->RunWithSettings( + base::Bind( + &ExtensionSettingDataTypeController:: + StartAssociationWithExtensionSettings, + this)); + return true; +} + +void ExtensionSettingDataTypeController::StartAssociationWithExtensionSettings( + ExtensionSettings* extension_settings) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + extension_settings_ = extension_settings; + // Calls CreateSyncComponents, which expects extension_settings_ to be + // non-NULL. + StartAssociation(); +} + +void ExtensionSettingDataTypeController::CreateSyncComponents() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK_EQ(state(), ASSOCIATING); + DCHECK(extension_settings_); + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory()->CreateExtensionSettingSyncComponents( + extension_settings_, profile_sync_service_, this); + set_model_associator(sync_components.model_associator); + set_change_processor(sync_components.change_processor); +} + +bool ExtensionSettingDataTypeController::StopAssociationAsync() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(state(), STOPPING); + if (!BrowserThread::PostTask( + BrowserThread::FILE, + FROM_HERE, + base::Bind( + &ExtensionSettingDataTypeController::StopAssociation, + this))) { + NOTREACHED(); + } + return true; +} + +void ExtensionSettingDataTypeController::RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message) { + UMA_HISTOGRAM_COUNTS("Sync.ExtensionSettingRunFailures", 1); +} + +void ExtensionSettingDataTypeController::RecordAssociationTime( + base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.ExtensionSettingAssociationTime", time); +} + +void ExtensionSettingDataTypeController::RecordStartFailure( + StartResult result) { + UMA_HISTOGRAM_ENUMERATION( + "Sync.ExtensionSettingStartFailures", result, MAX_START_RESULT); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/extension_setting_data_type_controller.h b/chrome/browser/sync/glue/extension_setting_data_type_controller.h new file mode 100644 index 0000000..9da3662 --- /dev/null +++ b/chrome/browser/sync/glue/extension_setting_data_type_controller.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011 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_SETTING_DATA_TYPE_CONTROLLER_H__ +#define CHROME_BROWSER_SYNC_GLUE_EXTENSION_SETTING_DATA_TYPE_CONTROLLER_H__ +#pragma once + +#include <string> + +#include "base/compiler_specific.h" +#include "chrome/browser/sync/glue/non_frontend_data_type_controller.h" + +class ExtensionSettings; +class ExtensionSettingsUIWrapper; +class Profile; +class ProfileSyncFactory; +class ProfileSyncService; + +namespace browser_sync { + +class ExtensionSettingDataTypeController + : public NonFrontendDataTypeController { + public: + ExtensionSettingDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* profile_sync_service); + virtual ~ExtensionSettingDataTypeController(); + + // NonFrontendDataTypeController implementation + virtual syncable::ModelType type() const OVERRIDE; + virtual browser_sync::ModelSafeGroup model_safe_group() const OVERRIDE; + + private: + // NonFrontendDataTypeController implementation. + virtual bool StartModels() OVERRIDE; + virtual bool StartAssociationAsync() OVERRIDE; + virtual void CreateSyncComponents() OVERRIDE; + virtual bool StopAssociationAsync() OVERRIDE; + virtual void RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message) OVERRIDE; + virtual void RecordAssociationTime(base::TimeDelta time) OVERRIDE; + virtual void RecordStartFailure(StartResult result) OVERRIDE; + + // Starts sync association with |extension_settings|. Callback from + // RunWithSettings of |extension_settings_ui_wrapper_| on FILE thread. + void StartAssociationWithExtensionSettings( + ExtensionSettings* extension_settings); + + // These only used on the UI thread. + ExtensionSettingsUIWrapper* extension_settings_ui_wrapper_; + ProfileSyncService* profile_sync_service_; + + // Only used on the FILE thread. + ExtensionSettings* extension_settings_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionSettingDataTypeController); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_SETTING_DATA_TYPE_CONTROLLER_H__ diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h index deb089a..d60f235 100644 --- a/chrome/browser/sync/profile_sync_factory.h +++ b/chrome/browser/sync/profile_sync_factory.h @@ -15,6 +15,7 @@ #include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/unrecoverable_error_handler.h" +class ExtensionSettings; class PersonalDataManager; class PasswordStore; class ProfileSyncService; @@ -96,6 +97,14 @@ class ProfileSyncFactory { browser_sync::UnrecoverableErrorHandler* error_handler) = 0; // Instantiates both a model associator and change processor for the + // extension setting data type. The pointers in the return struct are + // owned by the caller. + virtual SyncComponents CreateExtensionSettingSyncComponents( + ExtensionSettings* extension_settings, + ProfileSyncService* profile_sync_service, + 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( diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index d470ca7..7076e38 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -4,6 +4,7 @@ #include "base/command_line.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_settings.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url_service.h" #include "chrome/browser/search_engines/template_url_service_factory.h" @@ -19,6 +20,7 @@ #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_data_type_controller.h" +#include "chrome/browser/sync/glue/extension_setting_data_type_controller.h" #include "chrome/browser/sync/glue/generic_change_processor.h" #include "chrome/browser/sync/glue/password_change_processor.h" #include "chrome/browser/sync/glue/password_data_type_controller.h" @@ -42,6 +44,7 @@ #include "chrome/browser/webdata/web_data_service.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" +#include "content/browser/browser_thread.h" using browser_sync::AppDataTypeController; using browser_sync::AutofillChangeProcessor; @@ -56,6 +59,7 @@ using browser_sync::DataTypeController; using browser_sync::DataTypeManager; using browser_sync::DataTypeManagerImpl; using browser_sync::ExtensionDataTypeController; +using browser_sync::ExtensionSettingDataTypeController; using browser_sync::GenericChangeProcessor; using browser_sync::PasswordChangeProcessor; using browser_sync::PasswordDataTypeController; @@ -153,6 +157,13 @@ void ProfileSyncFactoryImpl::RegisterDataTypes(ProfileSyncService* pss) { new SessionDataTypeController(this, profile_, pss)); } + // Extension setting sync is disabled by default. Register only if + // explicitly enabled. + if (command_line_->HasSwitch(switches::kEnableSyncExtensionSettings)) { + pss->RegisterDataTypeController( + new ExtensionSettingDataTypeController(this, profile_, pss)); + } + if (!command_line_->HasSwitch(switches::kDisableSyncAutofillProfile)) { pss->RegisterDataTypeController( new AutofillProfileDataTypeController(this, profile_)); @@ -244,6 +255,22 @@ ProfileSyncFactoryImpl::CreateBookmarkSyncComponents( } ProfileSyncFactory::SyncComponents +ProfileSyncFactoryImpl::CreateExtensionSettingSyncComponents( + ExtensionSettings* extension_settings, + ProfileSyncService* profile_sync_service, + UnrecoverableErrorHandler* error_handler) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + sync_api::UserShare* user_share = profile_sync_service->GetUserShare(); + GenericChangeProcessor* change_processor = + new GenericChangeProcessor(extension_settings, error_handler, user_share); + browser_sync::SyncableServiceAdapter* sync_service_adapter = + new browser_sync::SyncableServiceAdapter(syncable::EXTENSION_SETTINGS, + extension_settings, + change_processor); + return SyncComponents(sync_service_adapter, change_processor); +} + +ProfileSyncFactory::SyncComponents ProfileSyncFactoryImpl::CreateExtensionSyncComponents( ProfileSyncService* profile_sync_service, UnrecoverableErrorHandler* error_handler) { diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h index 1403f5d..650f407 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.h +++ b/chrome/browser/sync/profile_sync_factory_impl.h @@ -12,6 +12,7 @@ #include "chrome/browser/sync/profile_sync_factory.h" class CommandLine; +class ExtensionSettings; class Profile; class ProfileSyncFactoryImpl : public ProfileSyncFactory { @@ -49,6 +50,11 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory { ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreateExtensionSettingSyncComponents( + ExtensionSettings* extension_settings, + ProfileSyncService* profile_sync_service, + browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreateExtensionSyncComponents( ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler); diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index ddefd39..801308a 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_mock.h @@ -11,6 +11,8 @@ #include "chrome/browser/sync/profile_sync_factory.h" #include "testing/gmock/include/gmock/gmock.h" +class ExtensionSettings; + namespace browser_sync { class AssociatorInterface; class ChangeProcessor; @@ -52,6 +54,10 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory { MOCK_METHOD2(CreateExtensionSyncComponents, SyncComponents(ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler)); + MOCK_METHOD3(CreateExtensionSettingSyncComponents, + SyncComponents(ExtensionSettings* extension_settings, + ProfileSyncService* profile_sync_service, + browser_sync::UnrecoverableErrorHandler* error_handler)); MOCK_METHOD3(CreatePasswordSyncComponents, SyncComponents( ProfileSyncService* profile_sync_service, diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 69b88ab..6600d76 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -315,6 +315,9 @@ void ProfileSyncService::RegisterPreferences() { pref_service->RegisterBooleanPref(prefs::kSyncTypedUrls, enable_by_default, PrefService::UNSYNCABLE_PREF); + pref_service->RegisterBooleanPref(prefs::kSyncExtensionSettings, + enable_by_default, + PrefService::UNSYNCABLE_PREF); pref_service->RegisterBooleanPref(prefs::kSyncExtensions, enable_by_default, PrefService::UNSYNCABLE_PREF); @@ -580,6 +583,8 @@ const char* ProfileSyncService::GetPrefNameForDataType( return prefs::kSyncThemes; case syncable::TYPED_URLS: return prefs::kSyncTypedUrls; + case syncable::EXTENSION_SETTINGS: + return prefs::kSyncExtensionSettings; case syncable::EXTENSIONS: return prefs::kSyncExtensions; case syncable::APPS: diff --git a/chrome/browser/sync/protocol/extension_setting_specifics.proto b/chrome/browser/sync/protocol/extension_setting_specifics.proto new file mode 100644 index 0000000..ff9f029 --- /dev/null +++ b/chrome/browser/sync/protocol/extension_setting_specifics.proto @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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 an extension setting. + +// Update proto_value_conversions{.h,.cc,_unittest.cc} if you change +// any fields in this file. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option retain_unknown_fields = true; + +package sync_pb; + +import "sync.proto"; + +// Properties of extension setting sync objects. +message ExtensionSettingSpecifics { + // Id of the extension the setting is for. + optional string extension_id = 1; + + // Setting key. + optional string key = 2; + + // Setting value serialized as JSON. + optional string value = 3; +} + +extend EntitySpecifics { + optional ExtensionSettingSpecifics extension_setting = 96159; +} diff --git a/chrome/browser/sync/protocol/nigori_specifics.proto b/chrome/browser/sync/protocol/nigori_specifics.proto index ba18e6c..5f4d21b 100644 --- a/chrome/browser/sync/protocol/nigori_specifics.proto +++ b/chrome/browser/sync/protocol/nigori_specifics.proto @@ -65,6 +65,8 @@ message NigoriSpecifics { // If true, all current and future datatypes will be encrypted. optional bool encrypt_everything = 24; + + optional bool encrypt_extension_settings = 25; } extend EntitySpecifics { diff --git a/chrome/browser/sync/protocol/proto_value_conversions.cc b/chrome/browser/sync/protocol/proto_value_conversions.cc index 1ffd123..38e40eb 100644 --- a/chrome/browser/sync/protocol/proto_value_conversions.cc +++ b/chrome/browser/sync/protocol/proto_value_conversions.cc @@ -15,6 +15,7 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/encryption.pb.h" +#include "chrome/browser/sync/protocol/extension_setting_specifics.pb.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" @@ -213,6 +214,15 @@ DictionaryValue* BookmarkSpecificsToValue( return value; } +DictionaryValue* ExtensionSettingSpecificsToValue( + const sync_pb::ExtensionSettingSpecifics& proto) { + DictionaryValue* value = new DictionaryValue(); + SET_STR(extension_id); + SET_STR(key); + SET_STR(value); + return value; +} + DictionaryValue* ExtensionSpecificsToValue( const sync_pb::ExtensionSpecifics& proto) { DictionaryValue* value = new DictionaryValue(); @@ -242,6 +252,7 @@ DictionaryValue* NigoriSpecificsToValue( SET_BOOL(encrypt_search_engines); SET_BOOL(sync_tabs); SET_BOOL(encrypt_everything); + SET_BOOL(encrypt_extension_settings); return value; } @@ -321,6 +332,7 @@ DictionaryValue* EntitySpecificsToValue( SET_EXTENSION(sync_pb, autofill_profile, AutofillProfileSpecificsToValue); SET_EXTENSION(sync_pb, bookmark, BookmarkSpecificsToValue); SET_EXTENSION(sync_pb, extension, ExtensionSpecificsToValue); + SET_EXTENSION(sync_pb, extension_setting, ExtensionSettingSpecificsToValue); SET_EXTENSION(sync_pb, nigori, NigoriSpecificsToValue); SET_EXTENSION(sync_pb, password, PasswordSpecificsToValue); SET_EXTENSION(sync_pb, preference, PreferenceSpecificsToValue); diff --git a/chrome/browser/sync/protocol/proto_value_conversions.h b/chrome/browser/sync/protocol/proto_value_conversions.h index e9da22c..5556d3f 100644 --- a/chrome/browser/sync/protocol/proto_value_conversions.h +++ b/chrome/browser/sync/protocol/proto_value_conversions.h @@ -19,6 +19,7 @@ class AutofillSpecifics; class BookmarkSpecifics; class EncryptedData; class EntitySpecifics; +class ExtensionSettingSpecifics; class ExtensionSpecifics; class NigoriSpecifics; class PasswordSpecifics; @@ -85,6 +86,9 @@ base::DictionaryValue* AutofillProfileSpecificsToValue( base::DictionaryValue* BookmarkSpecificsToValue( const sync_pb::BookmarkSpecifics& bookmark_specifics); +base::DictionaryValue* ExtensionSettingSpecificsToValue( + const sync_pb::ExtensionSettingSpecifics& extension_setting_specifics); + base::DictionaryValue* ExtensionSpecificsToValue( const sync_pb::ExtensionSpecifics& extension_specifics); diff --git a/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc b/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc index 2a1f4e8..9f85960 100644 --- a/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc +++ b/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc @@ -12,6 +12,7 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/encryption.pb.h" +#include "chrome/browser/sync/protocol/extension_setting_specifics.pb.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" @@ -44,7 +45,7 @@ TEST_F(ProtoValueConversionsTest, ProtoChangeCheck) { // If this number changes, that means we added or removed a data // type. Don't forget to add a unit test for {New // type}SpecificsToValue below. - EXPECT_EQ(14, syncable::MODEL_TYPE_COUNT); + EXPECT_EQ(15, syncable::MODEL_TYPE_COUNT); // We'd also like to check if we changed any field in our messages. // However, that's hard to do: sizeof could work, but it's @@ -99,6 +100,10 @@ TEST_F(ProtoValueConversionsTest, BookmarkSpecificsToValue) { TestSpecificsToValue(BookmarkSpecificsToValue); } +TEST_F(ProtoValueConversionsTest, ExtensionSettingSpecificsToValue) { + TestSpecificsToValue(ExtensionSettingSpecificsToValue); +} + TEST_F(ProtoValueConversionsTest, ExtensionSpecificsToValue) { TestSpecificsToValue(ExtensionSpecificsToValue); } @@ -144,6 +149,7 @@ TEST_F(ProtoValueConversionsTest, EntitySpecificsToValue) { SET_EXTENSION(autofill_profile); SET_EXTENSION(bookmark); SET_EXTENSION(extension); + SET_EXTENSION(extension_setting); SET_EXTENSION(nigori); SET_EXTENSION(password); SET_EXTENSION(preference); diff --git a/chrome/browser/sync/protocol/sync_proto.gyp b/chrome/browser/sync/protocol/sync_proto.gyp index ad60328..add6a9f 100644 --- a/chrome/browser/sync/protocol/sync_proto.gyp +++ b/chrome/browser/sync/protocol/sync_proto.gyp @@ -16,6 +16,7 @@ 'app_specifics.proto', 'autofill_specifics.proto', 'bookmark_specifics.proto', + 'extension_setting_specifics.proto', 'extension_specifics.proto', 'nigori_specifics.proto', 'password_specifics.proto', diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc index 8d4227d..9c186eb 100644 --- a/chrome/browser/sync/syncable/model_type.cc +++ b/chrome/browser/sync/syncable/model_type.cc @@ -11,6 +11,7 @@ #include "chrome/browser/sync/protocol/app_specifics.pb.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" +#include "chrome/browser/sync/protocol/extension_setting_specifics.pb.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" @@ -62,6 +63,9 @@ void AddDefaultExtensionValue(syncable::ModelType datatype, case APPS: specifics->MutableExtension(sync_pb::app); break; + case EXTENSION_SETTINGS: + specifics->MutableExtension(sync_pb::extension_setting); + break; default: NOTREACHED() << "No known extension for model type."; } @@ -115,6 +119,9 @@ int GetExtensionFieldNumberFromModelType(ModelType model_type) { case APPS: return sync_pb::kAppFieldNumber; break; + case EXTENSION_SETTINGS: + return sync_pb::kExtensionSettingFieldNumber; + break; default: NOTREACHED() << "No known extension for model type."; return 0; @@ -192,6 +199,9 @@ ModelType GetModelTypeFromSpecifics(const sync_pb::EntitySpecifics& specifics) { if (specifics.HasExtension(sync_pb::session)) return SESSIONS; + if (specifics.HasExtension(sync_pb::extension_setting)) + return EXTENSION_SETTINGS; + return UNSPECIFIED; } @@ -225,6 +235,8 @@ std::string ModelTypeToString(ModelType model_type) { return "Apps"; case AUTOFILL_PROFILE: return "Autofill Profiles"; + case EXTENSION_SETTINGS: + return "Extension settings"; default: break; } @@ -295,6 +307,8 @@ ModelType ModelTypeFromString(const std::string& model_type_string) { return SESSIONS; else if (model_type_string == "Apps") return APPS; + else if (model_type_string == "Extension settings") + return EXTENSION_SETTINGS; else NOTREACHED() << "No known model type corresponding to " << model_type_string << "."; @@ -387,6 +401,8 @@ std::string ModelTypeToRootTag(ModelType type) { return "google_chrome_apps"; case AUTOFILL_PROFILE: return "google_chrome_autofill_profiles"; + case EXTENSION_SETTINGS: + return "google_chrome_extension_settings"; default: break; } @@ -450,6 +466,10 @@ void PostTimeToTypeHistogram(ModelType model_type, base::TimeDelta time) { SYNC_FREQ_HISTOGRAM("Sync.FreqApps", time); return; } + case EXTENSION_SETTINGS: { + SYNC_FREQ_HISTOGRAM("Sync.FreqExtensionSettings", time); + return; + } default: LOG(ERROR) << "No known extension for model type."; } @@ -467,6 +487,7 @@ const char kAutofillNotificationType[] = "AUTOFILL"; const char kThemeNotificationType[] = "THEME"; const char kTypedUrlNotificationType[] = "TYPED_URL"; const char kExtensionNotificationType[] = "EXTENSION"; +const char kExtensionSettingNotificationType[] = "EXTENSION_SETTING"; const char kNigoriNotificationType[] = "NIGORI"; const char kAppNotificationType[] = "APP"; const char kSearchEngineNotificationType[] = "SEARCH_ENGINE"; @@ -513,6 +534,9 @@ bool RealModelTypeToNotificationType(ModelType model_type, case AUTOFILL_PROFILE: *notification_type = kAutofillProfileNotificationType; return true; + case EXTENSION_SETTINGS: + *notification_type = kExtensionSettingNotificationType; + return true; default: break; } @@ -558,6 +582,9 @@ bool NotificationTypeToRealModelType(const std::string& notification_type, } else if (notification_type == kAutofillProfileNotificationType) { *model_type = AUTOFILL_PROFILE; return true; + } else if (notification_type == kExtensionSettingNotificationType) { + *model_type = EXTENSION_SETTINGS; + return true; } *model_type = UNSPECIFIED; return false; diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h index b4285e5..09f4410 100644 --- a/chrome/browser/sync/syncable/model_type.h +++ b/chrome/browser/sync/syncable/model_type.h @@ -74,6 +74,8 @@ enum ModelType { SESSIONS, // An app folder or an app object. APPS, + // A setting from the extension settings API. + EXTENSION_SETTINGS, MODEL_TYPE_COUNT, }; diff --git a/chrome/browser/sync/util/cryptographer.cc b/chrome/browser/sync/util/cryptographer.cc index 8126f48..5e2a129 100644 --- a/chrome/browser/sync/util/cryptographer.cc +++ b/chrome/browser/sync/util/cryptographer.cc @@ -291,6 +291,8 @@ void Cryptographer::UpdateEncryptedTypesFromNigori( encrypted_types_.insert(syncable::THEMES); if (nigori.encrypt_typed_urls()) encrypted_types_.insert(syncable::TYPED_URLS); + if (nigori.encrypt_extension_settings()) + encrypted_types_.insert(syncable::EXTENSION_SETTINGS); if (nigori.encrypt_extensions()) encrypted_types_.insert(syncable::EXTENSIONS); if (nigori.encrypt_search_engines()) @@ -324,6 +326,8 @@ void Cryptographer::UpdateNigoriFromEncryptedTypes( nigori->set_encrypt_themes(encrypted_types_.count(syncable::THEMES) > 0); nigori->set_encrypt_typed_urls( encrypted_types_.count(syncable::TYPED_URLS) > 0); + nigori->set_encrypt_extension_settings( + encrypted_types_.count(syncable::EXTENSION_SETTINGS) > 0); nigori->set_encrypt_extensions( encrypted_types_.count(syncable::EXTENSIONS) > 0); nigori->set_encrypt_search_engines( diff --git a/chrome/browser/ui/cocoa/extensions/extension_popup_controller_unittest.mm b/chrome/browser/ui/cocoa/extensions/extension_popup_controller_unittest.mm index 88363dc..499c1a9 100644 --- a/chrome/browser/ui/cocoa/extensions/extension_popup_controller_unittest.mm +++ b/chrome/browser/ui/cocoa/extensions/extension_popup_controller_unittest.mm @@ -9,7 +9,6 @@ #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/extensions/extension_settings.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/cocoa/cocoa_test_helper.h" #include "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h" @@ -38,12 +37,10 @@ class ExtensionTestingProfile : public TestingProfile { extension_prefs_.reset(new ExtensionPrefs(GetPrefs(), GetExtensionsInstallDir(), extension_pref_value_map_.get())); - extension_settings_ = new ExtensionSettings(GetExtensionsSettingsDir()); service_.reset(new ExtensionService(this, CommandLine::ForCurrentProcess(), GetExtensionsInstallDir(), extension_prefs_.get(), - extension_settings_.get(), false, true)); service_->set_extensions_enabled(true); @@ -56,7 +53,6 @@ class ExtensionTestingProfile : public TestingProfile { manager_.reset(); service_.reset(); extension_prefs_.reset(); - extension_settings_ = NULL; } virtual ExtensionProcessManager* GetExtensionProcessManager() { @@ -70,7 +66,6 @@ class ExtensionTestingProfile : public TestingProfile { private: scoped_ptr<ExtensionProcessManager> manager_; scoped_ptr<ExtensionPrefs> extension_prefs_; - scoped_refptr<ExtensionSettings> extension_settings_; scoped_ptr<ExtensionService> service_; scoped_ptr<ExtensionPrefValueMap> extension_pref_value_map_; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 328ae5e..5b58dc4 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1069,8 +1069,12 @@ 'browser/extensions/extension_proxy_api_helpers.h', 'browser/extensions/extension_rlz_module.cc', 'browser/extensions/extension_rlz_module.h', + 'browser/extensions/extension_setting_sync_data.cc', + 'browser/extensions/extension_setting_sync_data.h', 'browser/extensions/extension_settings.cc', 'browser/extensions/extension_settings.h', + 'browser/extensions/extension_settings_ui_wrapper.cc', + 'browser/extensions/extension_settings_ui_wrapper.h', 'browser/extensions/extension_settings_api.cc', 'browser/extensions/extension_settings_api.h', 'browser/extensions/extension_settings_noop_storage.cc', @@ -1079,6 +1083,8 @@ 'browser/extensions/extension_settings_storage.h', 'browser/extensions/extension_settings_storage_cache.cc', 'browser/extensions/extension_settings_storage_cache.h', + 'browser/extensions/extension_settings_sync_util.cc', + 'browser/extensions/extension_settings_sync_util.h', 'browser/extensions/extension_settings_storage_quota_enforcer.cc', 'browser/extensions/extension_settings_storage_quota_enforcer.h', 'browser/extensions/extension_settings_leveldb_storage.cc', @@ -1170,6 +1176,8 @@ 'browser/extensions/pending_extension_manager.h', 'browser/extensions/sandboxed_extension_unpacker.cc', 'browser/extensions/sandboxed_extension_unpacker.h', + 'browser/extensions/syncable_extension_settings_storage.cc', + 'browser/extensions/syncable_extension_settings_storage.h', 'browser/extensions/theme_installed_infobar_delegate.cc', 'browser/extensions/theme_installed_infobar_delegate.h', 'browser/extensions/user_script_listener.cc', @@ -2084,6 +2092,8 @@ 'browser/sync/glue/do_optimistic_refresh_task.h', 'browser/sync/glue/extension_data_type_controller.cc', 'browser/sync/glue/extension_data_type_controller.h', + 'browser/sync/glue/extension_setting_data_type_controller.cc', + 'browser/sync/glue/extension_setting_data_type_controller.h', 'browser/sync/glue/frontend_data_type_controller.cc', 'browser/sync/glue/frontend_data_type_controller.h', 'browser/sync/glue/generic_change_processor.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 33c37c6..a7b47d3 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1243,6 +1243,13 @@ 'browser/extensions/extension_prefs_unittest.cc', 'browser/extensions/extension_process_manager_unittest.cc', 'browser/extensions/extension_proxy_api_helpers_unittest.cc', + 'browser/extensions/extension_settings_cached_leveldb_storage_unittest.cc', + 'browser/extensions/extension_settings_cached_noop_storage_unittest.cc', + 'browser/extensions/extension_settings_leveldb_storage_unittest.cc', + 'browser/extensions/extension_settings_quota_unittest.cc', + 'browser/extensions/extension_settings_storage_unittest.h', + 'browser/extensions/extension_settings_storage_unittest.cc', + 'browser/extensions/extension_settings_sync_unittest.cc', 'browser/extensions/extension_service_unittest.cc', 'browser/extensions/extension_service_unittest.h', 'browser/extensions/extension_special_storage_policy_unittest.cc', @@ -2318,12 +2325,6 @@ 'browser/extensions/extension_resource_request_policy_apitest.cc', 'browser/extensions/extension_rlz_apitest.cc', 'browser/extensions/extension_settings_apitest.cc', - 'browser/extensions/extension_settings_cached_leveldb_storage_unittest.cc', - 'browser/extensions/extension_settings_cached_noop_storage_unittest.cc', - 'browser/extensions/extension_settings_leveldb_storage_unittest.cc', - 'browser/extensions/extension_settings_quota_unittest.cc', - 'browser/extensions/extension_settings_storage_unittest.h', - 'browser/extensions/extension_settings_storage_unittest.cc', 'browser/extensions/extension_sidebar_apitest.cc', 'browser/extensions/extension_startup_browsertest.cc', 'browser/extensions/extension_storage_apitest.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index f45b0f5..779030a 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -503,6 +503,10 @@ const char kEnableShortcutsProvider[] = "enable-shortcuts-provider"; // On platforms that support it, enable smooth scroll animation. const char kEnableSmoothScrolling[] = "enable-smooth-scrolling"; + +// Enable syncing extension settings. +const char kEnableSyncExtensionSettings[] = "enable-sync-extension-settings"; + // Enable OAuth sign-in for sync. const char kEnableSyncOAuth[] = "enable-sync-oauth"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 4338e3d..d080fbd 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -145,6 +145,9 @@ extern const char kEnableResourceContentSettings[]; extern const char kEnableSearchProviderApiV2[]; extern const char kEnableShortcutsProvider[]; extern const char kEnableSmoothScrolling[]; +// TODO(kalman): Add to about:flags when UI for syncing extension settings has +// been figured out. +extern const char kEnableSyncExtensionSettings[]; extern const char kEnableSyncOAuth[]; extern const char kEnableSyncSearchEngines[]; extern const char kEnableSyncTabs[]; diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index ae2e69a..75471c6 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -1366,6 +1366,7 @@ const char kSyncAutofillProfile[] = "sync.autofill_profile"; const char kSyncThemes[] = "sync.themes"; const char kSyncTypedUrls[] = "sync.typed_urls"; const char kSyncExtensions[] = "sync.extensions"; +const char kSyncExtensionSettings[] = "sync.extension_settings"; const char kSyncSearchEngines[] = "sync.search_engines"; const char kSyncSessions[] = "sync.sessions"; diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index 9def19e..bb22ef7 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -494,6 +494,7 @@ extern const char kSyncAutofillProfile[]; extern const char kSyncThemes[]; extern const char kSyncTypedUrls[]; extern const char kSyncExtensions[]; +extern const char kSyncExtensionSettings[]; extern const char kSyncManaged[]; extern const char kSyncSearchEngines[]; extern const char kSyncSessions[]; diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc index 2c07469..d75727a 100644 --- a/chrome/test/base/testing_profile.cc +++ b/chrome/test/base/testing_profile.cc @@ -347,13 +347,10 @@ ExtensionService* TestingProfile::CreateExtensionService( new ExtensionPrefs(GetPrefs(), install_directory, extension_pref_value_map_.get())); - extension_settings_ = - new ExtensionSettings(GetPath().AppendASCII("Extension Settings")); extension_service_.reset(new ExtensionService(this, command_line, install_directory, extension_prefs_.get(), - extension_settings_.get(), autoupdate_enabled, true)); return extension_service_.get(); diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h index 1f9b094..39aefe2 100644 --- a/chrome/test/base/testing_profile.h +++ b/chrome/test/base/testing_profile.h @@ -34,7 +34,6 @@ class SpecialStoragePolicy; class AutocompleteClassifier; class BookmarkModel; class CommandLine; -class ExtensionSettings; class ExtensionPrefs; class ExtensionPrefStore; class ExtensionPrefValueMap; @@ -377,10 +376,6 @@ class TestingProfile : public Profile { // invoked. scoped_ptr<ExtensionPrefs> extension_prefs_; - // The Extension settings. Only created if CreateExtensionService is - // invoked. - scoped_refptr<ExtensionSettings> extension_settings_; - scoped_ptr<ExtensionService> extension_service_; scoped_ptr<ExtensionPrefValueMap> extension_pref_value_map_; diff --git a/net/tools/testserver/chromiumsync.py b/net/tools/testserver/chromiumsync.py index a8a939c..e1c049c 100755 --- a/net/tools/testserver/chromiumsync.py +++ b/net/tools/testserver/chromiumsync.py @@ -21,6 +21,7 @@ import urlparse import app_specifics_pb2 import autofill_specifics_pb2 import bookmark_specifics_pb2 +import extension_setting_specifics_pb2 import extension_specifics_pb2 import nigori_specifics_pb2 import password_specifics_pb2 @@ -48,7 +49,8 @@ ALL_TYPES = ( SEARCH_ENGINE, SESSION, THEME, - TYPED_URL) = range(13) + TYPED_URL, + EXTENSION_SETTINGS) = range(14) # Well-known server tag of the top level 'Google Chrome' folder. TOP_LEVEL_FOLDER_TAG = 'google_chrome' @@ -60,6 +62,7 @@ SYNC_TYPE_TO_EXTENSION = { AUTOFILL: autofill_specifics_pb2.autofill, AUTOFILL_PROFILE: autofill_specifics_pb2.autofill_profile, BOOKMARK: bookmark_specifics_pb2.bookmark, + EXTENSION_SETTINGS: extension_setting_specifics_pb2.extension_setting, EXTENSIONS: extension_specifics_pb2.extension, NIGORI: nigori_specifics_pb2.nigori, PASSWORD: password_specifics_pb2.password, @@ -384,6 +387,9 @@ class SyncDataModel(object): parent_tag='google_chrome', sync_type=AUTOFILL), PermanentItem('google_chrome_autofill_profiles', name='Autofill Profiles', parent_tag='google_chrome', sync_type=AUTOFILL_PROFILE), + PermanentItem('google_chrome_extension_settings', + name='Extension Settings', + parent_tag='google_chrome', sync_type=EXTENSION_SETTINGS), PermanentItem('google_chrome_extensions', name='Extensions', parent_tag='google_chrome', sync_type=EXTENSIONS), PermanentItem('google_chrome_passwords', name='Passwords', |