Commit f5ec20ca authored by ricow@chromium.org's avatar ricow@chromium.org

Manually revert r176051 and r176047, drover was merging incorrectly.

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176053 0039d316-1c4b-4281-b951-d872f2087c98
parent 78acaf6d
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/ui_test_utils.h"
namespace extensions {
// Times out on win asan, http://crbug.com/166026
#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
#define MAYBE_ContextMenus DISABLED_ContextMenus
#else
#define MAYBE_ContextMenus ContextMenus
#endif
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_ContextMenus) {
ASSERT_TRUE(RunExtensionTest("context_menus/basics")) << message_;
ASSERT_TRUE(RunExtensionTest("context_menus/no_perms")) << message_;
ASSERT_TRUE(RunExtensionTest("context_menus/item_ids")) << message_;
ASSERT_TRUE(RunExtensionTest("context_menus/event_page")) << message_;
}
// crbug.com/51436 -- creating context menus from multiple script contexts
// should work.
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ContextMenusFromMultipleContexts) {
ASSERT_TRUE(test_server()->Start());
ASSERT_TRUE(RunExtensionTest("context_menus/add_from_multiple_contexts"))
<< message_;
const extensions::Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
{
// Tell the extension to update the page action state.
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(),
extension->GetResourceURL("popup.html"));
ASSERT_TRUE(catcher.GetNextResult());
}
{
// Tell the extension to update the page action state again.
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(),
extension->GetResourceURL("popup2.html"));
ASSERT_TRUE(catcher.GetNextResult());
}
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_H_
#include "chrome/browser/extensions/extension_function.h"
namespace extensions {
class ContextMenusCreateFunction : public SyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("contextMenus.create")
protected:
virtual ~ContextMenusCreateFunction() {}
// ExtensionFunction:
virtual bool RunImpl() OVERRIDE;
};
class ContextMenusUpdateFunction : public SyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("contextMenus.update")
protected:
virtual ~ContextMenusUpdateFunction() {}
// ExtensionFunction:
virtual bool RunImpl() OVERRIDE;
};
class ContextMenusRemoveFunction : public SyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("contextMenus.remove")
protected:
virtual ~ContextMenusRemoveFunction() {}
// ExtensionFunction:
virtual bool RunImpl() OVERRIDE;
};
class ContextMenusRemoveAllFunction : public SyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("contextMenus.removeAll")
protected:
virtual ~ContextMenusRemoveAllFunction() {}
// ExtensionFunction:
virtual bool RunImpl() OVERRIDE;
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/leveldb_settings_storage_factory.h"
#include "base/logging.h"
#include "chrome/browser/value_store/leveldb_value_store.h"
namespace extensions {
ValueStore* LeveldbSettingsStorageFactory::Create(
const FilePath& base_path,
const std::string& extension_id) {
return new LeveldbValueStore(base_path.AppendASCII(extension_id));
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_LEVELDB_SETTINGS_STORAGE_FACTORY_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_LEVELDB_SETTINGS_STORAGE_FACTORY_H_
#include "chrome/browser/extensions/api/storage/settings_storage_factory.h"
namespace extensions {
// Factory for creating LeveldbValueStore instances.
class LeveldbSettingsStorageFactory : public SettingsStorageFactory {
public:
virtual ValueStore* Create(const FilePath& base_path,
const std::string& extension_id) OVERRIDE;
private:
// SettingsStorageFactory is refcounted.
virtual ~LeveldbSettingsStorageFactory() {}
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_LEVELDB_SETTINGS_STORAGE_FACTORY_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/managed_value_store_cache.h"
#include <set>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "chrome/browser/extensions/api/storage/policy_value_store.h"
#include "chrome/browser/extensions/api/storage/settings_storage_factory.h"
#include "chrome/browser/extensions/event_names.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/value_store/value_store_change.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace extensions {
ManagedValueStoreCache::ManagedValueStoreCache(
policy::PolicyService* policy_service,
EventRouter* event_router,
const scoped_refptr<SettingsStorageFactory>& factory,
const scoped_refptr<SettingsObserverList>& observers,
const FilePath& profile_path)
: ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
weak_this_on_ui_(weak_factory_.GetWeakPtr()),
policy_service_(policy_service),
event_router_(event_router),
storage_factory_(factory),
observers_(observers),
base_path_(profile_path.AppendASCII(
ExtensionService::kManagedSettingsDirectoryName)) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// |event_router| can be NULL on unit_tests.
if (event_router_)
event_router_->RegisterObserver(this, event_names::kOnSettingsChanged);
policy_service_->AddObserver(policy::POLICY_DOMAIN_EXTENSIONS, this);
}
ManagedValueStoreCache::~ManagedValueStoreCache() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!event_router_);
// Delete the PolicyValueStores on FILE.
store_map_.clear();
}
void ManagedValueStoreCache::ShutdownOnUI() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
policy_service_->RemoveObserver(policy::POLICY_DOMAIN_EXTENSIONS, this);
policy_service_ = NULL;
if (event_router_)
event_router_->UnregisterObserver(this);
event_router_ = NULL;
weak_factory_.InvalidateWeakPtrs();
}
void ManagedValueStoreCache::RunWithValueStoreForExtension(
const StorageCallback& callback,
scoped_refptr<const Extension> extension) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
PolicyValueStore* store = GetStoreFor(extension->id());
if (store) {
callback.Run(store);
} else {
// First time that an extension calls storage.managed.get(). Create the
// store and load it with the current policy, and don't send event
// notifications.
CreateStoreFor(
extension->id(),
false,
base::Bind(&ManagedValueStoreCache::RunWithValueStoreForExtension,
base::Unretained(this),
callback,
extension));
}
}
void ManagedValueStoreCache::DeleteStorageSoon(
const std::string& extension_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
PolicyValueStore* store = GetStoreFor(extension_id);
if (!store) {
// It's possible that the store exists, but hasn't been loaded yet
// (because the extension is unloaded, for example). Open the database to
// clear it if it exists.
// TODO(joaodasilva): move this check to a ValueStore method.
if (file_util::DirectoryExists(base_path_.AppendASCII(extension_id))) {
CreateStoreFor(
extension_id,
false,
base::Bind(&ManagedValueStoreCache::DeleteStorageSoon,
base::Unretained(this),
extension_id));
}
} else {
store->DeleteStorage();
store_map_.erase(extension_id);
}
}
void ManagedValueStoreCache::OnPolicyUpdated(policy::PolicyDomain domain,
const std::string& component_id,
const policy::PolicyMap& previous,
const policy::PolicyMap& current) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ManagedValueStoreCache::UpdatePolicyOnFILE,
base::Unretained(this),
std::string(component_id),
base::Passed(current.DeepCopy())));
}
void ManagedValueStoreCache::UpdatePolicyOnFILE(
const std::string& extension_id,
scoped_ptr<policy::PolicyMap> current_policy) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
PolicyValueStore* store = GetStoreFor(extension_id);
if (!store) {
// The extension hasn't executed any storage.managed.* calls, and isn't
// listening for onChanged() either. Ignore this notification in that case.
return;
}
// Update the policy on the backing store, and fire notifications if it
// changed.
store->SetCurrentPolicy(*current_policy, true);
}
void ManagedValueStoreCache::OnListenerAdded(
const EventListenerInfo& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK_EQ(std::string(event_names::kOnSettingsChanged), details.event_name);
// This is invoked on several occasions:
//
// 1. when an extension first registers to observe storage.onChanged; in this
// case the backend doesn't have any previous data persisted, and it won't
// trigger a notification.
//
// 2. when the browser starts up and all existing extensions re-register for
// the onChanged event. In this case, if the current policy differs from
// the persisted version then a notification will be sent.
//
// 3. a policy update just occurred and sent a notification, and an extension
// with EventPages that is observing onChanged just woke up and registed
// again. In this case the policy update already persisted the current
// policy version, and |store| already exists.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ManagedValueStoreCache::CreateForExtensionOnFILE,
base::Unretained(this),
details.extension_id));
}
void ManagedValueStoreCache::CreateForExtensionOnFILE(
const std::string& extension_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
PolicyValueStore* store = GetStoreFor(extension_id);
if (!store)
CreateStoreFor(extension_id, true, base::Closure());
}
PolicyValueStore* ManagedValueStoreCache::GetStoreFor(
const std::string& extension_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
PolicyValueStoreMap::iterator it = store_map_.find(extension_id);
if (it == store_map_.end())
return NULL;
return it->second.get();
}
void ManagedValueStoreCache::CreateStoreFor(
const std::string& extension_id,
bool notify_if_changed,
const base::Closure& continuation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!GetStoreFor(extension_id));
// Creating or loading an existing database requires an immediate update
// with the current policy for the corresponding extension, which must be
// retrieved on UI.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&ManagedValueStoreCache::GetInitialPolicy,
weak_this_on_ui_,
extension_id,
notify_if_changed,
continuation));
}
void ManagedValueStoreCache::GetInitialPolicy(
const std::string& extension_id,
bool notify_if_changed,
const base::Closure& continuation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const policy::PolicyMap& policy = policy_service_->GetPolicies(
policy::POLICY_DOMAIN_EXTENSIONS, extension_id);
// Now post back to FILE to create the database.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ManagedValueStoreCache::CreateStoreWithInitialPolicy,
base::Unretained(this),
extension_id,
notify_if_changed,
base::Passed(policy.DeepCopy()),
continuation));
}
void ManagedValueStoreCache::CreateStoreWithInitialPolicy(
const std::string& extension_id,
bool notify_if_changed,
scoped_ptr<policy::PolicyMap> initial_policy,
const base::Closure& continuation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// If a 2nd call to CreateStoreFor() is issued before the 1st gets to execute
// its UI task, then the 2nd will enter this function but the store has
// already been created. Check for that.
PolicyValueStore* store = GetStoreFor(extension_id);
if (!store) {
// Create it now.
// If the database doesn't exist yet then this is the initial install,
// and no notifications should be issued in that case.
// TODO(joaodasilva): move this check to a ValueStore method.
if (!file_util::DirectoryExists(base_path_.AppendASCII(extension_id)))
notify_if_changed = false;
store = new PolicyValueStore(
extension_id,
observers_,
make_scoped_ptr(storage_factory_->Create(base_path_, extension_id)));
store_map_[extension_id] = make_linked_ptr(store);
}
// Send the latest policy to the store.
store->SetCurrentPolicy(*initial_policy, notify_if_changed);
// And finally resume from where this process started.
if (!continuation.is_null())
continuation.Run();
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_MANAGED_VALUE_STORE_CACHE_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_MANAGED_VALUE_STORE_CACHE_H_
#include <map>
#include <string>
#include "base/basictypes.h"
#include "base/callback_forward.h"
#include "base/compiler_specific.h"
#include "base/file_path.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/extensions/api/storage/value_store_cache.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/policy/policy_service.h"
namespace policy {
class PolicyMap;
}
namespace extensions {
class PolicyValueStore;
class SettingsStorageFactory;
// A ValueStoreCache that manages a PolicyValueStore for each extension that
// uses the storage.managed namespace. This class observes policy changes and
// which extensions listen for storage.onChanged(), and sends the appropriate
// updates to the corresponding PolicyValueStore on the FILE thread.
class ManagedValueStoreCache : public ValueStoreCache,
public policy::PolicyService::Observer,
public EventRouter::Observer {
public:
// |policy_service| is used to retrieve policy for extensions, and to observe
// policy updates.
// ||event_router| is used to observe which extensions listen for onChanged.
// |factory| is used to create databases for the PolicyValueStores.
// |observers| is the list of SettingsObservers to notify when a ValueStore
// changes.
// |profile_path| is the path for the profile. The databases are created in
// a directory under this path.
ManagedValueStoreCache(policy::PolicyService* policy_service,
EventRouter* event_router,
const scoped_refptr<SettingsStorageFactory>& factory,
const scoped_refptr<SettingsObserverList>& observers,
const FilePath& profile_path);
virtual ~ManagedValueStoreCache();
private:
// Maps an extension ID to its PolicyValueStoreMap.
typedef std::map<std::string, linked_ptr<PolicyValueStore> >
PolicyValueStoreMap;
// ValueStoreCache implementation:
virtual void ShutdownOnUI() OVERRIDE;
virtual void RunWithValueStoreForExtension(
const StorageCallback& callback,
scoped_refptr<const Extension> extension) OVERRIDE;
virtual void DeleteStorageSoon(const std::string& extension_id) OVERRIDE;
// PolicyService::Observer implementation:
virtual void OnPolicyUpdated(policy::PolicyDomain domain,
const std::string& component_id,
const policy::PolicyMap& previous,
const policy::PolicyMap& current) OVERRIDE;
// Posted by OnPolicyUpdated() to update a PolicyValueStore on the FILE
// thread.
void UpdatePolicyOnFILE(const std::string& extension_id,
scoped_ptr<policy::PolicyMap> current_policy);
// EventRouter::Observer implementation:
virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE;
// Posted by OnListenerAdded() to load or create a PolicyValueStore for the
// given |extension_id|.
void CreateForExtensionOnFILE(const std::string& extension_id);
// Returns an existing PolicyValueStore for |extension_id|, or NULL.
PolicyValueStore* GetStoreFor(const std::string& extension_id);
// Creates a new PolicyValueStore for |extension_id|. This may open an
// existing database, or create a new one. This also sends the current policy
// for |extension_id| to the database. When |notify_if_changed| is true,
// a notification is sent with the changes between the current policy and the
// previously stored policy, if there are any.
//
// Since this is used on FILE but must retrieve the current policy, this
// method first posts GetInitialPolicy() to UI and then resumes in
// CreateStoreWithInitialPolicy(). If |continuation| is not null then it
// will be invoked after the store is created.
//
// CreateStoreFor() can be safely invoked from any method on the FILE thread.
// It posts to UI used |weak_this_on_ui_|, so that the task is dropped if
// ShutdownOnUI() has been invoked. Otherwise, GetInitialPolicy() executes
// on UI and can safely post CreateStoreWithInitialPolicy to FILE.
// CreateStoreWithInitialPolicy then guarantees that a store for
// |extension_id| exists or is created, and then executes the |continuation|;
// so when the |continuation| executes, a store for |extension_id| is
// guaranteed to exist.
void CreateStoreFor(const std::string& extension_id,
bool notify_if_changed,
const base::Closure& continuation);
// Helper for CreateStoreFor, invoked on UI.
void GetInitialPolicy(const std::string& extension_id,
bool notify_if_changed,
const base::Closure& continuation);
// Helper for CreateStoreFor, invoked on FILE.
void CreateStoreWithInitialPolicy(const std::string& extension_id,
bool notify_if_changed,
scoped_ptr<policy::PolicyMap> policy,
const base::Closure& continuation);
// Used to create a WeakPtr valid on the UI thread, so that FILE tasks can
// post back to UI.
base::WeakPtrFactory<ManagedValueStoreCache> weak_factory_;
// A WeakPtr to |this| that is valid on UI. This is used by tasks on the FILE
// thread to post back to UI.
base::WeakPtr<ManagedValueStoreCache> weak_this_on_ui_;
// The PolicyService that is observed for policy updates. Lives on UI.
policy::PolicyService* policy_service_;
// The EventRouter is created before the SettingsFrontend (which owns the
// instance of this class), and the SettingsFrontend is also destroyed before
// the EventRouter is. |event_router_| is thus valid for the lifetime of this
// object, until ShutdownOnUI() is invoked. Lives on UI.
EventRouter* event_router_;
// These live on the FILE thread.
scoped_refptr<SettingsStorageFactory> storage_factory_;
scoped_refptr<SettingsObserverList> observers_;
FilePath base_path_;
// All the PolicyValueStores live on the FILE thread, and |store_map_| can be
// accessed only on the FILE thread as well.
PolicyValueStoreMap store_map_;
DISALLOW_COPY_AND_ASSIGN(ManagedValueStoreCache);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_MANAGED_VALUE_STORE_CACHE_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/policy_value_store.h"
#include "base/logging.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "chrome/browser/policy/policy_map.h"
#include "chrome/browser/policy/policy_types.h"
#include "chrome/browser/value_store/value_store_change.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace extensions {
namespace {
const char kReadOnlyStoreErrorMessage[] = "This is a read-only store.";
ValueStore::WriteResult WriteResultError() {
return ValueStore::MakeWriteResult(kReadOnlyStoreErrorMessage);
}
} // namespace
PolicyValueStore::PolicyValueStore(
const std::string& extension_id,
const scoped_refptr<SettingsObserverList>& observers,
scoped_ptr<ValueStore> delegate)
: extension_id_(extension_id),
observers_(observers),
delegate_(delegate.Pass()) {}
PolicyValueStore::~PolicyValueStore() {}
void PolicyValueStore::SetCurrentPolicy(const policy::PolicyMap& policy,
bool notify_if_changed) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// Convert |policy| to a dictionary value. Only include mandatory policies
// for now.
base::DictionaryValue current_policy;
for (policy::PolicyMap::const_iterator it = policy.begin();
it != policy.end(); ++it) {
if (it->second.level == policy::POLICY_LEVEL_MANDATORY) {
current_policy.SetWithoutPathExpansion(
it->first, it->second.value->DeepCopy());
}
}
// Get the previous policies stored in the database.
// TODO(joaodasilva): it'd be better to have a less expensive way of
// determining which keys are currently stored, or of determining which keys
// must be removed.
base::DictionaryValue previous_policy;
ValueStore::ReadResult read_result = delegate_->Get();
if (read_result->HasError()) {
LOG(WARNING) << "Failed to read managed settings for extension "
<< extension_id_ << ": " << read_result->error();
// Leave |previous_policy| empty, so that events are generated for every
// policy in |current_policy|.
} else {
read_result->settings()->Swap(&previous_policy);
}
// Now get two lists of changes: changes after setting the current policies,
// and changes after removing old policies that aren't in |current_policy|
// anymore.
std::vector<std::string> removed_keys;
for (base::DictionaryValue::Iterator it(previous_policy);
it.HasNext(); it.Advance()) {
if (!current_policy.HasKey(it.key()))
removed_keys.push_back(it.key());
}
ValueStoreChangeList changes;
WriteResult result = delegate_->Remove(removed_keys);
if (!result->HasError()) {
changes.insert(
changes.end(), result->changes().begin(), result->changes().end());
}
// IGNORE_QUOTA because these settings aren't writable by the extension, and
// are configured by the domain administrator.
ValueStore::WriteOptions options = ValueStore::IGNORE_QUOTA;
result = delegate_->Set(options, current_policy);
if (!result->HasError()) {
changes.insert(
changes.end(), result->changes().begin(), result->changes().end());
}
if (!changes.empty() && notify_if_changed) {
observers_->Notify(
&SettingsObserver::OnSettingsChanged,
extension_id_,
settings_namespace::MANAGED,
ValueStoreChange::ToJson(changes));
}
}
void PolicyValueStore::DeleteStorage() {
// This is called from our owner, indicating that storage for this extension
// should be removed.
delegate_->Clear();
}
size_t PolicyValueStore::GetBytesInUse(const std::string& key) {
// LeveldbValueStore doesn't implement this; and the underlying database
// isn't acccessible to the extension in any case; from the extension's
// perspective this is a read-only store.
return 0;
}
size_t PolicyValueStore::GetBytesInUse(const std::vector<std::string>& keys) {
// See note above.
return 0;
}
size_t PolicyValueStore::GetBytesInUse() {
// See note above.
return 0;
}
ValueStore::ReadResult PolicyValueStore::Get(const std::string& key) {
return delegate_->Get(key);
}
ValueStore::ReadResult PolicyValueStore::Get(
const std::vector<std::string>& keys) {
return delegate_->Get(keys);
}
ValueStore::ReadResult PolicyValueStore::Get() {
return delegate_->Get();
}
ValueStore::WriteResult PolicyValueStore::Set(
WriteOptions options, const std::string& key, const base::Value& value) {
return WriteResultError();
}
ValueStore::WriteResult PolicyValueStore::Set(
WriteOptions options, const base::DictionaryValue& settings) {
return WriteResultError();
}
ValueStore::WriteResult PolicyValueStore::Remove(const std::string& key) {
return WriteResultError();
}
ValueStore::WriteResult PolicyValueStore::Remove(
const std::vector<std::string>& keys) {
return WriteResultError();
}
ValueStore::WriteResult PolicyValueStore::Clear() {
return WriteResultError();
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_POLICY_VALUE_STORE_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_POLICY_VALUE_STORE_H_
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/value_store/value_store.h"
namespace policy {
class PolicyMap;
}
namespace extensions {
// A ValueStore that is backed by another, persistent ValueStore, and stores
// the policies for a specific extension there. This ValueStore is used to
// run the function of the storage.managed namespace; it's read-only for the
// extension. The ManagedValueStoreCache sends updated policy to this store
// and manages its lifetime.
class PolicyValueStore : public ValueStore {
public:
PolicyValueStore(const std::string& extension_id,
const scoped_refptr<SettingsObserverList>& observers,
scoped_ptr<ValueStore> delegate);
virtual ~PolicyValueStore();
// Stores |policy| in the persistent database represented by the |delegate_|.
// If |notify_if_changed| and |policy| differs from the previously persisted
// version, then a notification is sent to the |observers_| with a list of the
// changes detected.
void SetCurrentPolicy(const policy::PolicyMap& policy,
bool notify_if_changed);
// Clears all the stored data and deletes the database.
void DeleteStorage();
// ValueStore implementation:
virtual size_t GetBytesInUse(const std::string& key) OVERRIDE;
virtual size_t GetBytesInUse(const std::vector<std::string>& keys) OVERRIDE;
virtual size_t GetBytesInUse() OVERRIDE;
virtual ReadResult Get(const std::string& key) OVERRIDE;
virtual ReadResult Get(const std::vector<std::string>& keys) OVERRIDE;
virtual ReadResult Get() OVERRIDE;
virtual WriteResult Set(
WriteOptions options,
const std::string& key,
const base::Value& value) OVERRIDE;
virtual WriteResult Set(
WriteOptions options, const base::DictionaryValue& values) OVERRIDE;
virtual WriteResult Remove(const std::string& key) OVERRIDE;
virtual WriteResult Remove(const std::vector<std::string>& keys) OVERRIDE;
virtual WriteResult Clear() OVERRIDE;
// For unit tests.
ValueStore* delegate() { return delegate_.get(); }
private:
std::string extension_id_;
scoped_refptr<SettingsObserverList> observers_;
scoped_ptr<ValueStore> delegate_;
DISALLOW_COPY_AND_ASSIGN(PolicyValueStore);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_POLICY_VALUE_STORE_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/policy_value_store.h"
#include "base/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/policy/policy_map.h"
#include "chrome/browser/value_store/leveldb_value_store.h"
#include "chrome/browser/value_store/value_store_unittest.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Mock;
namespace extensions {
namespace {
const char kTestExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
class MockSettingsObserver : public SettingsObserver {
public:
MOCK_METHOD3(OnSettingsChanged, void(
const std::string& extension_id,
settings_namespace::Namespace settings_namespace,
const std::string& changes_json));
};
// Extends PolicyValueStore by overriding the mutating methods, so that the
// Get() base implementation can be tested with the ValueStoreTest parameterized
// tests.
class MutablePolicyValueStore : public PolicyValueStore {
public:
explicit MutablePolicyValueStore(const FilePath& path)
: PolicyValueStore(kTestExtensionId,
make_scoped_refptr(new SettingsObserverList()),
scoped_ptr<ValueStore>(new LeveldbValueStore(path))) {}
virtual ~MutablePolicyValueStore() {}
virtual WriteResult Set(
WriteOptions options,
const std::string& key,
const base::Value& value) OVERRIDE {
return delegate()->Set(options, key, value);
}
virtual WriteResult Set(
WriteOptions options, const base::DictionaryValue& values) OVERRIDE {
return delegate()->Set(options, values);
}
virtual WriteResult Remove(const std::string& key) OVERRIDE {
return delegate()->Remove(key);
}
virtual WriteResult Remove(const std::vector<std::string>& keys) OVERRIDE {
return delegate()->Remove(keys);
}
virtual WriteResult Clear() OVERRIDE {
return delegate()->Clear();
}
private:
DISALLOW_COPY_AND_ASSIGN(MutablePolicyValueStore);
};
ValueStore* Param(const FilePath& file_path) {
return new MutablePolicyValueStore(file_path);
}
} // namespace
INSTANTIATE_TEST_CASE_P(
PolicyValueStoreTest,
ValueStoreTest,
testing::Values(&Param));
class PolicyValueStoreTest : public testing::Test {
public:
PolicyValueStoreTest()
: file_thread_(content::BrowserThread::FILE, &loop_) {}
virtual ~PolicyValueStoreTest() {}
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
observers_ = new SettingsObserverList();
observers_->AddObserver(&observer_);
store_.reset(new PolicyValueStore(
kTestExtensionId,
observers_,
scoped_ptr<ValueStore>(
new LeveldbValueStore(scoped_temp_dir_.path()))));
}
virtual void TearDown() OVERRIDE {
observers_->RemoveObserver(&observer_);
store_.reset();
}
protected:
base::ScopedTempDir scoped_temp_dir_;
MessageLoop loop_;
content::TestBrowserThread file_thread_;
scoped_ptr<PolicyValueStore> store_;
MockSettingsObserver observer_;
scoped_refptr<SettingsObserverList> observers_;
};
TEST_F(PolicyValueStoreTest, DontProvideRecommendedPolicies) {
policy::PolicyMap policies;
base::FundamentalValue expected(123);
policies.Set("must", policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, expected.DeepCopy());
policies.Set("may", policy::POLICY_LEVEL_RECOMMENDED,
policy::POLICY_SCOPE_USER, base::Value::CreateIntegerValue(456));
store_->SetCurrentPolicy(policies, false);
ValueStore::ReadResult result = store_->Get();
ASSERT_FALSE(result->HasError());
EXPECT_EQ(1u, result->settings()->size());
base::Value* value = NULL;
EXPECT_FALSE(result->settings()->Get("may", &value));
EXPECT_TRUE(result->settings()->Get("must", &value));
EXPECT_TRUE(base::Value::Equals(&expected, value));
}
TEST_F(PolicyValueStoreTest, ReadOnly) {
ValueStore::WriteOptions options = ValueStore::DEFAULTS;
base::StringValue string_value("value");
EXPECT_TRUE(store_->Set(options, "key", string_value)->HasError());
base::DictionaryValue dict;
dict.SetString("key", "value");
EXPECT_TRUE(store_->Set(options, dict)->HasError());
EXPECT_TRUE(store_->Remove("key")->HasError());
std::vector<std::string> keys;
keys.push_back("key");
EXPECT_TRUE(store_->Remove(keys)->HasError());
EXPECT_TRUE(store_->Clear()->HasError());
}
TEST_F(PolicyValueStoreTest, NotifyOnChanges) {
policy::PolicyMap policies;
policies.Set("aaa", policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
base::Value::CreateStringValue("111"));
EXPECT_CALL(observer_, OnSettingsChanged(_, _, _)).Times(0);
// No notification when setting the initial policy.
store_->SetCurrentPolicy(policies, false);
loop_.RunUntilIdle();
Mock::VerifyAndClearExpectations(&observer_);
// And no notifications on changes when not asked for.
policies.Set("aaa", policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
base::Value::CreateStringValue("222"));
policies.Set("bbb", policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
base::Value::CreateStringValue("223"));
EXPECT_CALL(observer_, OnSettingsChanged(_, _, _)).Times(0);
store_->SetCurrentPolicy(policies, false);
loop_.RunUntilIdle();
Mock::VerifyAndClearExpectations(&observer_);
// Notify when new policies are added.
ValueStoreChangeList changes;
base::StringValue value("333");
changes.push_back(ValueStoreChange("ccc", NULL, value.DeepCopy()));
policies.Set("ccc", policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
value.DeepCopy());
EXPECT_CALL(observer_, OnSettingsChanged(kTestExtensionId,
settings_namespace::MANAGED,
ValueStoreChange::ToJson(changes)));
store_->SetCurrentPolicy(policies, true);
loop_.RunUntilIdle();
Mock::VerifyAndClearExpectations(&observer_);
// Notify when policies change.
changes.clear();
base::StringValue new_value("444");
changes.push_back(
ValueStoreChange("ccc", value.DeepCopy(), new_value.DeepCopy()));
policies.Set("ccc", policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
new_value.DeepCopy());
EXPECT_CALL(observer_, OnSettingsChanged(kTestExtensionId,
settings_namespace::MANAGED,
ValueStoreChange::ToJson(changes)));
store_->SetCurrentPolicy(policies, true);
loop_.RunUntilIdle();
Mock::VerifyAndClearExpectations(&observer_);
// Notify when policies are removed.
changes.clear();
changes.push_back(ValueStoreChange("ccc", new_value.DeepCopy(), NULL));
policies.Erase("ccc");
EXPECT_CALL(observer_, OnSettingsChanged(kTestExtensionId,
settings_namespace::MANAGED,
ValueStoreChange::ToJson(changes)));
store_->SetCurrentPolicy(policies, true);
loop_.RunUntilIdle();
Mock::VerifyAndClearExpectations(&observer_);
// Don't notify when there aren't changes.
EXPECT_CALL(observer_, OnSettingsChanged(_, _, _)).Times(0);
store_->SetCurrentPolicy(policies, true);
loop_.RunUntilIdle();
Mock::VerifyAndClearExpectations(&observer_);
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/setting_sync_data.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "sync/api/sync_data.h"
#include "sync/protocol/app_setting_specifics.pb.h"
#include "sync/protocol/extension_setting_specifics.pb.h"
#include "sync/protocol/sync.pb.h"
namespace extensions {
SettingSyncData::SettingSyncData(
const syncer::SyncChange& sync_change) {
Init(sync_change.change_type(), sync_change.sync_data());
}
SettingSyncData::SettingSyncData(
const syncer::SyncData& sync_data) {
Init(syncer::SyncChange::ACTION_INVALID, sync_data);
}
void SettingSyncData::Init(
syncer::SyncChange::SyncChangeType change_type,
const syncer::SyncData& sync_data) {
DCHECK(!internal_.get());
sync_pb::EntitySpecifics specifics = sync_data.GetSpecifics();
// The data must only be either extension or app specfics.
DCHECK_NE(specifics.has_extension_setting(),
specifics.has_app_setting());
if (specifics.has_extension_setting()) {
InitFromExtensionSettingSpecifics(
change_type,
specifics.extension_setting());
} else if (specifics.has_app_setting()) {
InitFromExtensionSettingSpecifics(
change_type,
specifics.app_setting().extension_setting());
}
}
void SettingSyncData::InitFromExtensionSettingSpecifics(
syncer::SyncChange::SyncChangeType change_type,
const sync_pb::ExtensionSettingSpecifics& specifics) {
DCHECK(!internal_.get());
scoped_ptr<Value> value(
base::JSONReader::Read(specifics.value()));
if (!value.get()) {
LOG(WARNING) << "Specifics for " << specifics.extension_id() << "/" <<
specifics.key() << " had bad JSON for value: " << specifics.value();
value.reset(new DictionaryValue());
}
internal_ = new Internal(
change_type,
specifics.extension_id(),
specifics.key(),
value.Pass());
}
SettingSyncData::SettingSyncData(
syncer::SyncChange::SyncChangeType change_type,
const std::string& extension_id,
const std::string& key,
scoped_ptr<Value> value)
: internal_(new Internal(change_type, extension_id, key, value.Pass())) {}
SettingSyncData::~SettingSyncData() {}
syncer::SyncChange::SyncChangeType SettingSyncData::change_type() const {
return internal_->change_type_;
}
const std::string& SettingSyncData::extension_id() const {
return internal_->extension_id_;
}
const std::string& SettingSyncData::key() const {
return internal_->key_;
}
const Value& SettingSyncData::value() const {
return *internal_->value_;
}
SettingSyncData::Internal::Internal(
syncer::SyncChange::SyncChangeType change_type,
const std::string& extension_id,
const std::string& key,
scoped_ptr<Value> value)
: change_type_(change_type),
extension_id_(extension_id),
key_(key),
value_(value.Pass()) {
DCHECK(value_.get());
}
SettingSyncData::Internal::~Internal() {}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTING_SYNC_DATA_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTING_SYNC_DATA_H_
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
#include "sync/api/sync_change.h"
namespace syncer {
class SyncData;
}
namespace sync_pb {
class ExtensionSettingSpecifics;
}
namespace extensions {
// Container for data interpreted from sync data/changes for an extension or
// app setting. Safe and efficient to copy.
class SettingSyncData {
public:
// Creates from a sync change.
explicit SettingSyncData(const syncer::SyncChange& sync_change);
// Creates from sync data. |change_type| will be ACTION_INVALID.
explicit SettingSyncData(const syncer::SyncData& sync_data);
// Creates explicitly.
SettingSyncData(
syncer::SyncChange::SyncChangeType change_type,
const std::string& extension_id,
const std::string& key,
scoped_ptr<Value> value);
~SettingSyncData();
// Returns the type of the sync change; may be ACTION_INVALID.
syncer::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:
Internal(
syncer::SyncChange::SyncChangeType change_type,
const std::string& extension_id,
const std::string& key,
scoped_ptr<Value> value);
syncer::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 for an extension or app setting.
void Init(syncer::SyncChange::SyncChangeType change_type,
const syncer::SyncData& sync_data);
// Initializes internal_ from extension specifics.
void InitFromExtensionSettingSpecifics(
syncer::SyncChange::SyncChangeType change_type,
const sync_pb::ExtensionSettingSpecifics& specifics);
scoped_refptr<Internal> internal_;
};
typedef std::vector<SettingSyncData> SettingSyncDataList;
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTING_SYNC_DATA_H_
This diff is collapsed.
This diff is collapsed.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_BACKEND_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_BACKEND_H_
#include <map>
#include <set>
#include <string>
#include "base/compiler_specific.h"
#include "base/file_path.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/extensions/api/storage/settings_storage_factory.h"
#include "chrome/browser/extensions/api/storage/settings_storage_quota_enforcer.h"
#include "sync/api/syncable_service.h"
namespace syncer {
class SyncErrorFactory;
}
namespace extensions {
class SettingsSyncProcessor;
class SyncableSettingsStorage;
// Manages ValueStore objects for extensions, including routing
// changes from sync to them.
// Lives entirely on the FILE thread.
class SettingsBackend : public syncer::SyncableService {
public:
// |storage_factory| is use to create leveldb storage areas.
// |base_path| is the base of the extension settings directory, so the
// databases will be at base_path/extension_id.
// |observers| is the list of observers to settings changes.
SettingsBackend(
const scoped_refptr<SettingsStorageFactory>& storage_factory,
const FilePath& base_path,
const SettingsStorageQuotaEnforcer::Limits& quota,
const scoped_refptr<SettingsObserverList>& observers);
virtual ~SettingsBackend();
// Gets a weak reference to the storage area for |extension_id|.
// Must be run on the FILE thread.
ValueStore* GetStorage(const std::string& extension_id) const;
// Deletes all setting data for an extension. Call on the FILE thread.
void DeleteStorage(const std::string& extension_id);
// syncer::SyncableService implementation.
virtual syncer::SyncDataList GetAllSyncData(
syncer::ModelType type) const OVERRIDE;
virtual syncer::SyncMergeResult MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) OVERRIDE;
virtual syncer::SyncError ProcessSyncChanges(
const tracked_objects::Location& from_here,
const syncer::SyncChangeList& change_list) OVERRIDE;
virtual void StopSyncing(syncer::ModelType type) OVERRIDE;
private:
// Gets a weak reference to the storage area for a given extension,
// initializing sync with some initial data if sync enabled.
SyncableSettingsStorage* GetOrCreateStorageWithSyncData(
const std::string& extension_id,
const DictionaryValue& sync_data) const;
// Gets all extension IDs known to extension settings. This may not be all
// installed extensions.
std::set<std::string> GetKnownExtensionIDs() const;
// Creates a new SettingsSyncProcessor for an extension.
scoped_ptr<SettingsSyncProcessor> CreateSettingsSyncProcessor(
const std::string& extension_id) const;
// The Factory to use for creating leveldb storage areas.
const scoped_refptr<SettingsStorageFactory> storage_factory_;
// The base file path to create any leveldb databases at.
const FilePath base_path_;
// Quota limits (see SettingsStorageQuotaEnforcer).
const SettingsStorageQuotaEnforcer::Limits quota_;
// The list of observers to settings changes.
const scoped_refptr<SettingsObserverList> observers_;
// A cache of ValueStore objects that have already been created.
// Ensure that there is only ever one created per extension.
typedef std::map<std::string, linked_ptr<SyncableSettingsStorage> >
StorageObjMap;
mutable StorageObjMap storage_objs_;
// Current sync model type. Will be UNSPECIFIED if sync hasn't been enabled
// yet, and either SETTINGS or APP_SETTINGS if it has been.
syncer::ModelType sync_type_;
// Current sync processor, if any.
scoped_ptr<syncer::SyncChangeProcessor> sync_processor_;
// Current sync error handler if any.
scoped_ptr<syncer::SyncErrorFactory> sync_error_factory_;
DISALLOW_COPY_AND_ASSIGN(SettingsBackend);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_BACKEND_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/settings_frontend.h"
#include <limits>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/file_path.h"
#include "base/json/json_reader.h"
#include "chrome/browser/extensions/api/storage/leveldb_settings_storage_factory.h"
#include "chrome/browser/extensions/api/storage/settings_backend.h"
#include "chrome/browser/extensions/api/storage/sync_or_local_value_store_cache.h"
#include "chrome/browser/extensions/event_names.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/storage.h"
#include "content/public/browser/browser_thread.h"
#if defined(ENABLE_CONFIGURATION_POLICY)
#include "chrome/browser/extensions/api/storage/managed_value_store_cache.h"
#endif
using content::BrowserThread;
namespace extensions {
namespace {
// Settings change Observer which forwards changes on to the extension
// processes for |profile| and its incognito partner if it exists.
class DefaultObserver : public SettingsObserver {
public:
explicit DefaultObserver(Profile* profile) : profile_(profile) {}
// SettingsObserver implementation.
virtual void OnSettingsChanged(
const std::string& extension_id,
settings_namespace::Namespace settings_namespace,
const std::string& change_json) OVERRIDE {
// TODO(gdk): This is a temporary hack while the refactoring for
// string-based event payloads is removed. http://crbug.com/136045
scoped_ptr<ListValue> args(new ListValue());
args->Append(base::JSONReader::Read(change_json));
args->Append(Value::CreateStringValue(settings_namespace::ToString(
settings_namespace)));
scoped_ptr<Event> event(new Event(
event_names::kOnSettingsChanged, args.Pass()));
ExtensionSystem::Get(profile_)->event_router()->
DispatchEventToExtension(extension_id, event.Pass());
}
private:
Profile* const profile_;
};
SettingsStorageQuotaEnforcer::Limits GetLocalLimits() {
SettingsStorageQuotaEnforcer::Limits limits = {
static_cast<size_t>(api::storage::local::QUOTA_BYTES),
std::numeric_limits<size_t>::max(),
std::numeric_limits<size_t>::max()
};
return limits;
}
SettingsStorageQuotaEnforcer::Limits GetSyncLimits() {
SettingsStorageQuotaEnforcer::Limits limits = {
static_cast<size_t>(api::storage::sync::QUOTA_BYTES),
static_cast<size_t>(api::storage::sync::QUOTA_BYTES_PER_ITEM),
static_cast<size_t>(api::storage::sync::MAX_ITEMS)
};
return limits;
}
} // namespace
// static
SettingsFrontend* SettingsFrontend::Create(Profile* profile) {
return new SettingsFrontend(new LeveldbSettingsStorageFactory(), profile);
}
// static
SettingsFrontend* SettingsFrontend::Create(
const scoped_refptr<SettingsStorageFactory>& storage_factory,
Profile* profile) {
return new SettingsFrontend(storage_factory, profile);
}
SettingsFrontend::SettingsFrontend(
const scoped_refptr<SettingsStorageFactory>& factory, Profile* profile)
: local_quota_limit_(GetLocalLimits()),
sync_quota_limit_(GetSyncLimits()),
profile_(profile),
observers_(new SettingsObserverList()),
profile_observer_(new DefaultObserver(profile)) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!profile->IsOffTheRecord());
observers_->AddObserver(profile_observer_.get());
const FilePath& profile_path = profile->GetPath();
caches_[settings_namespace::LOCAL] =
new SyncOrLocalValueStoreCache(
settings_namespace::LOCAL,
factory,
local_quota_limit_,
observers_,
profile_path);
caches_[settings_namespace::SYNC] =
new SyncOrLocalValueStoreCache(
settings_namespace::SYNC,
factory,
sync_quota_limit_,
observers_,
profile_path);
#if defined(ENABLE_CONFIGURATION_POLICY)
caches_[settings_namespace::MANAGED] =
new ManagedValueStoreCache(
profile->GetPolicyService(),
ExtensionSystem::Get(profile)->event_router(),
factory,
observers_,
profile_path);
#endif
}
SettingsFrontend::~SettingsFrontend() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
observers_->RemoveObserver(profile_observer_.get());
for (CacheMap::iterator it = caches_.begin(); it != caches_.end(); ++it) {
ValueStoreCache* cache = it->second;
cache->ShutdownOnUI();
BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, cache);
}
}
syncer::SyncableService* SettingsFrontend::GetBackendForSync(
syncer::ModelType type) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
CacheMap::const_iterator it = caches_.find(settings_namespace::SYNC);
DCHECK(it != caches_.end());
const SyncOrLocalValueStoreCache* sync_cache =
static_cast<const SyncOrLocalValueStoreCache*>(it->second);
switch (type) {
case syncer::APP_SETTINGS:
return sync_cache->GetAppBackend();
case syncer::EXTENSION_SETTINGS:
return sync_cache->GetExtensionBackend();
default:
NOTREACHED();
return NULL;
}
}
bool SettingsFrontend::IsStorageEnabled(
settings_namespace::Namespace settings_namespace) const {
return caches_.find(settings_namespace) != caches_.end();
}
void SettingsFrontend::RunWithStorage(
const std::string& extension_id,
settings_namespace::Namespace settings_namespace,
const ValueStoreCache::StorageCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ValueStoreCache* cache = caches_[settings_namespace];
CHECK(cache);
// The |extension| has already been referenced earlier in the stack, so it
// can't be gone here.
// TODO(kalman): change RunWithStorage() to take a
// scoped_refptr<const Extension> instead.
scoped_refptr<const Extension> extension =
extensions::ExtensionSystem::Get(profile_)->extension_service()->
GetExtensionById(extension_id, true);
CHECK(extension);
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ValueStoreCache::RunWithValueStoreForExtension,
base::Unretained(cache), callback, extension));
}
void SettingsFrontend::DeleteStorageSoon(
const std::string& extension_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
for (CacheMap::iterator it = caches_.begin(); it != caches_.end(); ++it) {
ValueStoreCache* cache = it->second;
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ValueStoreCache::DeleteStorageSoon,
base::Unretained(cache),
extension_id));
}
}
scoped_refptr<SettingsObserverList> SettingsFrontend::GetObservers() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return observers_;
}
void SettingsFrontend::DisableStorageForTesting(
settings_namespace::Namespace settings_namespace) {
CacheMap::iterator it = caches_.find(settings_namespace);
if (it != caches_.end()) {
ValueStoreCache* cache = it->second;
cache->ShutdownOnUI();
BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, cache);
caches_.erase(it);
}
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_FRONTEND_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_FRONTEND_H_
#include <map>
#include <string>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/extensions/api/storage/settings_storage_factory.h"
#include "chrome/browser/extensions/api/storage/settings_storage_quota_enforcer.h"
#include "chrome/browser/extensions/api/storage/value_store_cache.h"
#include "sync/api/syncable_service.h"
class Profile;
namespace extensions {
// The component of extension settings which runs on the UI thread, as opposed
// to SettingsBackend which lives on the FILE thread.
// All public methods, must be called on the UI thread, with the exception of
// GetBackendForSync(), which must be called on the FILE thread.
class SettingsFrontend {
public:
// Creates with the default factory.
static SettingsFrontend* Create(Profile* profile);
// Creates with a specific factory |storage_factory| (presumably for tests).
static SettingsFrontend* Create(
const scoped_refptr<SettingsStorageFactory>& storage_factory,
Profile* profile);
virtual ~SettingsFrontend();
// Must only be called from the FILE thread. |type| should be either
// APP_SETTINGS or EXTENSION_SETTINGS.
syncer::SyncableService* GetBackendForSync(syncer::ModelType type) const;
// Returns true if |settings_namespace| is a valid namespace.
bool IsStorageEnabled(settings_namespace::Namespace settings_namespace) const;
// Runs |callback| with the storage area of the given |settings_namespace|
// for the |extension_id|.
void RunWithStorage(
const std::string& extension_id,
settings_namespace::Namespace settings_namespace,
const ValueStoreCache::StorageCallback& callback);
// Deletes the settings for the given |extension_id|.
void DeleteStorageSoon(const std::string& extension_id);
// Gets the thread-safe observer list.
scoped_refptr<SettingsObserverList> GetObservers();
void DisableStorageForTesting(
settings_namespace::Namespace settings_namespace);
private:
typedef std::map<settings_namespace::Namespace, ValueStoreCache*> CacheMap;
SettingsFrontend(
const scoped_refptr<SettingsStorageFactory>& storage_factory,
Profile* profile);
// The quota limit configurations for the local and sync areas, taken out of
// the schema in chrome/common/extensions/api/storage.json.
const SettingsStorageQuotaEnforcer::Limits local_quota_limit_;
const SettingsStorageQuotaEnforcer::Limits sync_quota_limit_;
// The (non-incognito) Profile this Frontend belongs to.
Profile* const profile_;
// List of observers to settings changes.
scoped_refptr<SettingsObserverList> observers_;
// Observer for |profile_|.
scoped_ptr<SettingsObserver> profile_observer_;
// Maps a known namespace to its corresponding ValueStoreCache. The caches
// are owned by this object.
CacheMap caches_;
DISALLOW_COPY_AND_ASSIGN(SettingsFrontend);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_FRONTEND_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "base/logging.h"
namespace extensions {
namespace settings_namespace {
namespace {
const char kLocalNamespace[] = "local";
const char kSyncNamespace[] = "sync";
const char kManagedNamespace[] = "managed";
} // namespace
std::string ToString(Namespace settings_namespace) {
switch (settings_namespace) {
case LOCAL: return kLocalNamespace;
case SYNC: return kSyncNamespace;
case MANAGED: return kManagedNamespace;
case INVALID: break;
}
NOTREACHED();
return std::string();
}
Namespace FromString(const std::string& namespace_string) {
if (namespace_string == kLocalNamespace)
return LOCAL;
if (namespace_string == kSyncNamespace)
return SYNC;
if (namespace_string == kManagedNamespace)
return MANAGED;
return INVALID;
}
} // namespace settings_namespace
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_NAMESPACE_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_NAMESPACE_H_
#include <string>
namespace extensions {
namespace settings_namespace {
// The namespaces of the storage areas.
enum Namespace {
LOCAL, // "local" i.e. chrome.storage.local
SYNC, // "sync" i.e. chrome.storage.sync
MANAGED, // "managed" i.e. chrome.storage.managed
INVALID
};
// Converts a namespace to its string representation.
// Namespace must not be INVALID.
std::string ToString(Namespace settings_namespace);
// Converts a string representation of a namespace to its namespace, or INVALID
// if the string doesn't map to one.
Namespace FromString(const std::string& ns_string);
} // namespace settings_namespace
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_NAMESPACE_H_
// 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_API_STORAGE_SETTINGS_OBSERVER_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_OBSERVER_H_
#include "base/observer_list_threadsafe.h"
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
namespace extensions {
// Interface for classes that listen to changes to extension settings.
class SettingsObserver {
public:
// Called when a list of settings have changed for an extension.
virtual void OnSettingsChanged(
const std::string& extension_id,
settings_namespace::Namespace settings_namespace,
const std::string& changes_json) = 0;
virtual ~SettingsObserver() {}
};
typedef ObserverListThreadSafe<SettingsObserver>
SettingsObserverList;
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_OBSERVER_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_STORAGE_FACTORY_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_STORAGE_FACTORY_H_
#include <string>
#include "base/file_path.h"
#include "base/memory/ref_counted.h"
class ValueStore;
namespace extensions {
// Factory for creating SettingStorage instances.
//
// Refcouted because it's just too messy to distribute these objects between
// SettingsBackend instances any other way.
class SettingsStorageFactory
: public base::RefCountedThreadSafe<SettingsStorageFactory> {
public:
// Creates a new ValueStore area for an extension under |base_path|.
// Return NULL to indicate failure. Must be called on the FILE thread.
virtual ValueStore* Create(const FilePath& base_path,
const std::string& extension_id) = 0;
protected:
friend class base::RefCountedThreadSafe<SettingsStorageFactory>;
virtual ~SettingsStorageFactory() {}
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_STORAGE_FACTORY_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/settings_storage_quota_enforcer.h"
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "chrome/common/extensions/api/extension_api.h"
#include "extensions/common/error_utils.h"
namespace extensions {
namespace {
const char* kQuotaExceededError = "* quota exceeded.";
// Resources there are a quota for.
enum Resource {
QUOTA_BYTES,
QUOTA_BYTES_PER_ITEM,
MAX_ITEMS
};
// Allocates a setting in a record of total and per-setting usage.
void Allocate(
const std::string& key,
const Value& value,
size_t* used_total,
std::map<std::string, size_t>* used_per_setting) {
// Calculate the setting size based on its JSON serialization size.
// TODO(kalman): Does this work with different encodings?
// TODO(kalman): This is duplicating work that the leveldb delegate
// implementation is about to do, and it would be nice to avoid this.
std::string value_as_json;
base::JSONWriter::Write(&value, &value_as_json);
size_t new_size = key.size() + value_as_json.size();
size_t existing_size = (*used_per_setting)[key];
*used_total += (new_size - existing_size);
(*used_per_setting)[key] = new_size;
}
// Frees the allocation of a setting in a record of total and per-setting usage.
void Free(
size_t* used_total,
std::map<std::string, size_t>* used_per_setting,
const std::string& key) {
*used_total -= (*used_per_setting)[key];
used_per_setting->erase(key);
}
// Returns an error result and logs the quota exceeded to UMA.
ValueStore::WriteResult QuotaExceededFor(Resource resource) {
std::string name;
switch (resource) {
case QUOTA_BYTES:
name = "QUOTA_BYTES";
UMA_HISTOGRAM_COUNTS_100(
"Extensions.SettingsQuotaExceeded.TotalBytes", 1);
break;
case QUOTA_BYTES_PER_ITEM:
name = "QUOTA_BYTES_PER_ITEM";
UMA_HISTOGRAM_COUNTS_100(
"Extensions.SettingsQuotaExceeded.BytesPerSetting", 1);
break;
case MAX_ITEMS:
name = "MAX_ITEMS";
UMA_HISTOGRAM_COUNTS_100(
"Extensions.SettingsQuotaExceeded.KeyCount", 1);
break;
default:
NOTREACHED();
}
return ValueStore::MakeWriteResult(
ErrorUtils::FormatErrorMessage(kQuotaExceededError, name));
}
} // namespace
SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
const Limits& limits, ValueStore* delegate)
: limits_(limits), delegate_(delegate), used_total_(0) {
ReadResult maybe_settings = delegate_->Get();
if (maybe_settings->HasError()) {
LOG(WARNING) << "Failed to get initial settings for quota: " <<
maybe_settings->error();
return;
}
for (DictionaryValue::Iterator it(*maybe_settings->settings().get());
it.HasNext(); it.Advance()) {
Allocate(
it.key(), it.value(), &used_total_, &used_per_setting_);
}
}
SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {}
size_t SettingsStorageQuotaEnforcer::GetBytesInUse(const std::string& key) {
std::map<std::string, size_t>::iterator maybe_used =
used_per_setting_.find(key);
return maybe_used == used_per_setting_.end() ? 0u : maybe_used->second;
}
size_t SettingsStorageQuotaEnforcer::GetBytesInUse(
const std::vector<std::string>& keys) {
size_t used = 0;
for (std::vector<std::string>::const_iterator it = keys.begin();
it != keys.end(); ++it) {
used += GetBytesInUse(*it);
}
return used;
}
size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
// All ValueStore implementations rely on GetBytesInUse being
// implemented here.
return used_total_;
}
ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
const std::string& key) {
return delegate_->Get(key);
}
ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
const std::vector<std::string>& keys) {
return delegate_->Get(keys);
}
ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get() {
return delegate_->Get();
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
WriteOptions options, const std::string& key, const Value& value) {
size_t new_used_total = used_total_;
std::map<std::string, size_t> new_used_per_setting = used_per_setting_;
Allocate(key, value, &new_used_total, &new_used_per_setting);
if (!(options & IGNORE_QUOTA)) {
if (new_used_total > limits_.quota_bytes) {
return QuotaExceededFor(QUOTA_BYTES);
}
if (new_used_per_setting[key] > limits_.quota_bytes_per_item) {
return QuotaExceededFor(QUOTA_BYTES_PER_ITEM);
}
if (new_used_per_setting.size() > limits_.max_items) {
return QuotaExceededFor(MAX_ITEMS);
}
}
WriteResult result = delegate_->Set(options, key, value);
if (result->HasError()) {
return result.Pass();
}
used_total_ = new_used_total;
used_per_setting_.swap(new_used_per_setting);
return result.Pass();
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
WriteOptions options, const DictionaryValue& values) {
size_t new_used_total = used_total_;
std::map<std::string, size_t> new_used_per_setting = used_per_setting_;
for (DictionaryValue::Iterator it(values); it.HasNext(); it.Advance()) {
Allocate(it.key(), it.value(), &new_used_total, &new_used_per_setting);
if (!(options & IGNORE_QUOTA) &&
new_used_per_setting[it.key()] > limits_.quota_bytes_per_item) {
return QuotaExceededFor(QUOTA_BYTES_PER_ITEM);
}
}
if (!(options & IGNORE_QUOTA)) {
if (new_used_total > limits_.quota_bytes) {
return QuotaExceededFor(QUOTA_BYTES);
}
if (new_used_per_setting.size() > limits_.max_items) {
return QuotaExceededFor(MAX_ITEMS);
}
}
WriteResult result = delegate_->Set(options, values);
if (result->HasError()) {
return result.Pass();
}
used_total_ = new_used_total;
used_per_setting_ = new_used_per_setting;
return result.Pass();
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
const std::string& key) {
WriteResult result = delegate_->Remove(key);
if (result->HasError()) {
return result.Pass();
}
Free(&used_total_, &used_per_setting_, key);
return result.Pass();
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
const std::vector<std::string>& keys) {
WriteResult result = delegate_->Remove(keys);
if (result->HasError()) {
return result.Pass();
}
for (std::vector<std::string>::const_iterator it = keys.begin();
it != keys.end(); ++it) {
Free(&used_total_, &used_per_setting_, *it);
}
return result.Pass();
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Clear() {
WriteResult result = delegate_->Clear();
if (result->HasError()) {
return result.Pass();
}
while (!used_per_setting_.empty()) {
Free(&used_total_, &used_per_setting_, used_per_setting_.begin()->first);
}
return result.Pass();
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
#include "base/compiler_specific.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/value_store/value_store.h"
namespace extensions {
// Enforces total quota and a per-setting quota in bytes, and a maximum number
// of setting keys, for a delegate storage area.
class SettingsStorageQuotaEnforcer : public ValueStore {
public:
struct Limits {
// The total quota in bytes.
size_t quota_bytes;
// The quota for each individual item in bytes.
size_t quota_bytes_per_item;
// The maximum number of items allowed.
size_t max_items;
};
SettingsStorageQuotaEnforcer(const Limits& limits, ValueStore* delegate);
virtual ~SettingsStorageQuotaEnforcer();
// ValueStore implementation.
virtual size_t GetBytesInUse(const std::string& key) OVERRIDE;
virtual size_t GetBytesInUse(const std::vector<std::string>& keys) OVERRIDE;
virtual size_t GetBytesInUse() OVERRIDE;
virtual ReadResult Get(const std::string& key) OVERRIDE;
virtual ReadResult Get(const std::vector<std::string>& keys) OVERRIDE;
virtual ReadResult Get() OVERRIDE;
virtual WriteResult Set(
WriteOptions options,
const std::string& key,
const Value& value) OVERRIDE;
virtual WriteResult Set(
WriteOptions options, const DictionaryValue& values) OVERRIDE;
virtual WriteResult Remove(const std::string& key) OVERRIDE;
virtual WriteResult Remove(const std::vector<std::string>& keys) OVERRIDE;
virtual WriteResult Clear() OVERRIDE;
private:
// Limits configuration.
const Limits limits_;
// The delegate storage area.
scoped_ptr<ValueStore> const delegate_;
// Total bytes in used by |delegate_|. Includes both key lengths and
// JSON-encoded values.
size_t used_total_;
// Map of item key to its size, including the key itself.
std::map<std::string, size_t> used_per_setting_;
DISALLOW_COPY_AND_ASSIGN(SettingsStorageQuotaEnforcer);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "chrome/browser/extensions/api/storage/settings_sync_processor.h"
#include "chrome/browser/extensions/api/storage/settings_sync_util.h"
#include "content/public/browser/browser_thread.h"
#include "sync/api/sync_change_processor.h"
#include "sync/api/sync_data.h"
#include "sync/protocol/extension_setting_specifics.pb.h"
using content::BrowserThread;
namespace extensions {
SettingsSyncProcessor::SettingsSyncProcessor(
const std::string& extension_id,
syncer::ModelType type,
syncer::SyncChangeProcessor* sync_processor)
: extension_id_(extension_id),
type_(type),
sync_processor_(sync_processor),
initialized_(false) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
CHECK(type == syncer::EXTENSION_SETTINGS || type == syncer::APP_SETTINGS);
CHECK(sync_processor);
}
SettingsSyncProcessor::~SettingsSyncProcessor() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
}
void SettingsSyncProcessor::Init(const DictionaryValue& initial_state) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
CHECK(!initialized_) << "Init called multiple times";
for (DictionaryValue::Iterator i(initial_state); i.HasNext(); i.Advance())
synced_keys_.insert(i.key());
initialized_ = true;
}
syncer::SyncError SettingsSyncProcessor::SendChanges(
const ValueStoreChangeList& changes) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
CHECK(initialized_) << "Init not called";
syncer::SyncChangeList sync_changes;
std::set<std::string> added_keys;
std::set<std::string> deleted_keys;
for (ValueStoreChangeList::const_iterator i = changes.begin();
i != changes.end(); ++i) {
const std::string& key = i->key();
const Value* value = i->new_value();
if (value) {
if (synced_keys_.count(key)) {
// New value, key is synced; send ACTION_UPDATE.
sync_changes.push_back(settings_sync_util::CreateUpdate(
extension_id_, key, *value, type_));
} else {
// New value, key is not synced; send ACTION_ADD.
sync_changes.push_back(settings_sync_util::CreateAdd(
extension_id_, key, *value, type_));
added_keys.insert(key);
}
} else {
if (synced_keys_.count(key)) {
// Clearing value, key is synced; send ACTION_DELETE.
sync_changes.push_back(settings_sync_util::CreateDelete(
extension_id_, key, type_));
deleted_keys.insert(key);
} else {
LOG(WARNING) << "Deleted " << key << " but not in synced_keys_";
}
}
}
if (sync_changes.empty())
return syncer::SyncError();
syncer::SyncError error =
sync_processor_->ProcessSyncChanges(FROM_HERE, sync_changes);
if (error.IsSet())
return error;
synced_keys_.insert(added_keys.begin(), added_keys.end());
for (std::set<std::string>::iterator i = deleted_keys.begin();
i != deleted_keys.end(); ++i) {
synced_keys_.erase(*i);
}
return syncer::SyncError();
}
void SettingsSyncProcessor::NotifyChanges(const ValueStoreChangeList& changes) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
CHECK(initialized_) << "Init not called";
for (ValueStoreChangeList::const_iterator i = changes.begin();
i != changes.end(); ++i) {
if (i->new_value())
synced_keys_.insert(i->key());
else
synced_keys_.erase(i->key());
}
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_SYNC_PROCESSOR_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_SYNC_PROCESSOR_H_
#include <set>
#include <string>
#include "chrome/browser/value_store/value_store_change.h"
#include "sync/api/sync_error.h"
namespace syncer {
class SyncChangeProcessor;
} // namespace syncer
namespace extensions {
// A wrapper for a SyncChangeProcessor that deals specifically with the syncing
// of a single extension's settings. Handles:
// - translating SettingChanges into calls into the Sync API.
// - deciding whether to ADD/REMOVE/SET depending on the current state of
// settings.
// - rate limiting (inherently per-extension, which is what we want).
class SettingsSyncProcessor {
public:
SettingsSyncProcessor(const std::string& extension_id,
syncer::ModelType type,
syncer::SyncChangeProcessor* sync_processor);
~SettingsSyncProcessor();
// Initializes this with the initial state of sync.
void Init(const DictionaryValue& initial_state);
// Sends |changes| to sync.
syncer::SyncError SendChanges(const ValueStoreChangeList& changes);
// Informs this that |changes| have been receieved from sync. No action will
// be taken, but this must be notified for internal bookkeeping.
void NotifyChanges(const ValueStoreChangeList& changes);
syncer::ModelType type() { return type_; }
private:
// ID of the extension the changes are for.
const std::string extension_id_;
// Sync model type. Either EXTENSION_SETTING or APP_SETTING.
const syncer::ModelType type_;
// The sync processor used to send changes to sync.
syncer::SyncChangeProcessor* const sync_processor_;
// Whether Init() has been called.
bool initialized_;
// Keys of the settings that are currently being synced. Used to decide what
// kind of action (ADD, UPDATE, REMOVE) to send to sync.
std::set<std::string> synced_keys_;
DISALLOW_COPY_AND_ASSIGN(SettingsSyncProcessor);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_SYNC_PROCESSOR_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/settings_sync_util.h"
#include "base/json/json_writer.h"
#include "base/values.h"
#include "sync/protocol/app_setting_specifics.pb.h"
#include "sync/protocol/extension_setting_specifics.pb.h"
#include "sync/protocol/sync.pb.h"
namespace extensions {
namespace settings_sync_util {
namespace {
void PopulateExtensionSettingSpecifics(
const std::string& extension_id,
const std::string& key,
const Value& value,
sync_pb::ExtensionSettingSpecifics* specifics) {
specifics->set_extension_id(extension_id);
specifics->set_key(key);
{
std::string value_as_json;
base::JSONWriter::Write(&value, &value_as_json);
specifics->set_value(value_as_json);
}
}
void PopulateAppSettingSpecifics(
const std::string& extension_id,
const std::string& key,
const Value& value,
sync_pb::AppSettingSpecifics* specifics) {
PopulateExtensionSettingSpecifics(
extension_id, key, value, specifics->mutable_extension_setting());
}
} // namespace
syncer::SyncData CreateData(
const std::string& extension_id,
const std::string& key,
const Value& value,
syncer::ModelType type) {
sync_pb::EntitySpecifics specifics;
switch (type) {
case syncer::EXTENSION_SETTINGS:
PopulateExtensionSettingSpecifics(
extension_id,
key,
value,
specifics.mutable_extension_setting());
break;
case syncer::APP_SETTINGS:
PopulateAppSettingSpecifics(
extension_id,
key,
value,
specifics.mutable_app_setting());
break;
default:
NOTREACHED();
}
return syncer::SyncData::CreateLocalData(
extension_id + "/" + key, key, specifics);
}
syncer::SyncChange CreateAdd(
const std::string& extension_id,
const std::string& key,
const Value& value,
syncer::ModelType type) {
return syncer::SyncChange(
FROM_HERE,
syncer::SyncChange::ACTION_ADD,
CreateData(extension_id, key, value, type));
}
syncer::SyncChange CreateUpdate(
const std::string& extension_id,
const std::string& key,
const Value& value,
syncer::ModelType type) {
return syncer::SyncChange(
FROM_HERE,
syncer::SyncChange::ACTION_UPDATE,
CreateData(extension_id, key, value, type));
}
syncer::SyncChange CreateDelete(
const std::string& extension_id,
const std::string& key,
syncer::ModelType type) {
DictionaryValue no_value;
return syncer::SyncChange(
FROM_HERE,
syncer::SyncChange::ACTION_DELETE,
CreateData(extension_id, key, no_value, type));
}
} // namespace settings_sync_util
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_SYNC_UTIL_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_SYNC_UTIL_H_
#include "sync/api/sync_change.h"
#include "sync/api/sync_data.h"
namespace base {
class Value;
} // namespace base
namespace extensions {
namespace settings_sync_util {
// Creates a syncer::SyncData object for an extension or app setting.
syncer::SyncData CreateData(
const std::string& extension_id,
const std::string& key,
const base::Value& value,
syncer::ModelType type);
// Creates an "add" sync change for an extension or app setting.
syncer::SyncChange CreateAdd(
const std::string& extension_id,
const std::string& key,
const base::Value& value,
syncer::ModelType type);
// Creates an "update" sync change for an extension or app setting.
syncer::SyncChange CreateUpdate(
const std::string& extension_id,
const std::string& key,
const base::Value& value,
syncer::ModelType type);
// Creates a "delete" sync change for an extension or app setting.
syncer::SyncChange CreateDelete(
const std::string& extension_id,
const std::string& key,
syncer::ModelType type);
} // namespace settings_sync_util
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_SYNC_UTIL_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/settings_test_util.h"
#include "base/file_path.h"
#include "chrome/browser/extensions/api/storage/settings_frontend.h"
#include "chrome/browser/extensions/extension_system_factory.h"
#include "chrome/common/extensions/extension.h"
namespace extensions {
namespace settings_test_util {
// Intended as a StorageCallback from GetStorage.
static void AssignStorage(ValueStore** dst, ValueStore* src) {
*dst = src;
}
ValueStore* GetStorage(
const std::string& extension_id,
settings_namespace::Namespace settings_namespace,
SettingsFrontend* frontend) {
ValueStore* storage = NULL;
frontend->RunWithStorage(
extension_id,
settings_namespace,
base::Bind(&AssignStorage, &storage));
MessageLoop::current()->RunUntilIdle();
return storage;
}
ValueStore* GetStorage(
const std::string& extension_id, SettingsFrontend* frontend) {
return GetStorage(extension_id, settings_namespace::SYNC, frontend);
}
// MockExtensionService
MockExtensionService::MockExtensionService() {}
MockExtensionService::~MockExtensionService() {}
const Extension* MockExtensionService::GetExtensionById(
const std::string& id, bool include_disabled) const {
std::map<std::string, scoped_refptr<Extension> >::const_iterator
maybe_extension = extensions_.find(id);
return maybe_extension == extensions_.end() ?
NULL : maybe_extension->second.get();
}
void MockExtensionService::AddExtensionWithId(
const std::string& id, Extension::Type type) {
std::set<std::string> empty_permissions;
AddExtensionWithIdAndPermissions(id, type, empty_permissions);
}
void MockExtensionService::AddExtensionWithIdAndPermissions(
const std::string& id,
Extension::Type type,
const std::set<std::string>& permissions_set) {
DictionaryValue manifest;
manifest.SetString("name", std::string("Test extension ") + id);
manifest.SetString("version", "1.0");
scoped_ptr<ListValue> permissions(new ListValue());
for (std::set<std::string>::const_iterator it = permissions_set.begin();
it != permissions_set.end(); ++it) {
permissions->Append(Value::CreateStringValue(*it));
}
manifest.Set("permissions", permissions.release());
switch (type) {
case Extension::TYPE_EXTENSION:
break;
case Extension::TYPE_LEGACY_PACKAGED_APP: {
DictionaryValue* app = new DictionaryValue();
DictionaryValue* app_launch = new DictionaryValue();
app_launch->SetString("local_path", "fake.html");
app->Set("launch", app_launch);
manifest.Set("app", app);
break;
}
default:
NOTREACHED();
}
std::string error;
scoped_refptr<Extension> extension(Extension::Create(
FilePath(),
Extension::INTERNAL,
manifest,
Extension::NO_FLAGS,
id,
&error));
DCHECK(extension.get());
DCHECK(error.empty());
extensions_[id] = extension;
for (std::set<std::string>::const_iterator it = permissions_set.begin();
it != permissions_set.end(); ++it) {
DCHECK(extension->HasAPIPermission(*it));
}
}
// MockExtensionSystem
MockExtensionSystem::MockExtensionSystem(Profile* profile)
: TestExtensionSystem(profile) {}
MockExtensionSystem::~MockExtensionSystem() {}
EventRouter* MockExtensionSystem::event_router() {
if (!event_router_.get())
event_router_.reset(new EventRouter(profile_, NULL));
return event_router_.get();
}
ExtensionService* MockExtensionSystem::extension_service() {
ExtensionServiceInterface* as_interface =
static_cast<ExtensionServiceInterface*>(&extension_service_);
return static_cast<ExtensionService*>(as_interface);
}
ProfileKeyedService* BuildMockExtensionSystem(Profile* profile) {
return new MockExtensionSystem(profile);
}
// MockProfile
MockProfile::MockProfile(const FilePath& file_path)
: TestingProfile(file_path) {
ExtensionSystemFactory::GetInstance()->SetTestingFactoryAndUse(this,
&BuildMockExtensionSystem);
}
MockProfile::~MockProfile() {}
// ScopedSettingsFactory
ScopedSettingsStorageFactory::ScopedSettingsStorageFactory() {}
ScopedSettingsStorageFactory::ScopedSettingsStorageFactory(
const scoped_refptr<SettingsStorageFactory>& delegate)
: delegate_(delegate) {}
ScopedSettingsStorageFactory::~ScopedSettingsStorageFactory() {}
void ScopedSettingsStorageFactory::Reset(
const scoped_refptr<SettingsStorageFactory>& delegate) {
delegate_ = delegate;
}
ValueStore* ScopedSettingsStorageFactory::Create(
const FilePath& base_path,
const std::string& extension_id) {
DCHECK(delegate_.get());
return delegate_->Create(base_path, extension_id);
}
} // namespace settings_test_util
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_TEST_UTIL_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_TEST_UTIL_H_
#include <set>
#include <string>
#include "base/compiler_specific.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "chrome/browser/extensions/api/storage/settings_storage_factory.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/test/base/testing_profile.h"
class ValueStore;
namespace extensions {
class SettingsFrontend;
// Utilities for extension settings API tests.
namespace settings_test_util {
// Synchronously gets the storage area for an extension from |frontend|.
ValueStore* GetStorage(
const std::string& extension_id,
settings_namespace::Namespace setting_namespace,
SettingsFrontend* frontend);
// Synchronously gets the SYNC storage for an extension from |frontend|.
ValueStore* GetStorage(
const std::string& extension_id,
SettingsFrontend* frontend);
// An ExtensionService which allows extensions to be hand-added to be returned
// by GetExtensionById.
class MockExtensionService : public TestExtensionService {
public:
MockExtensionService();
virtual ~MockExtensionService();
// Adds an extension with id |id| to be returned by GetExtensionById.
void AddExtensionWithId(const std::string& id, Extension::Type type);
// Adds an extension with id |id| to be returned by GetExtensionById, with
// a set of permissions.
void AddExtensionWithIdAndPermissions(
const std::string& id,
Extension::Type type,
const std::set<std::string>& permissions);
virtual const Extension* GetExtensionById(
const std::string& id, bool include_disabled) const OVERRIDE;
private:
std::map<std::string, scoped_refptr<Extension> > extensions_;
};
// A mock ExtensionSystem to serve an EventRouter.
class MockExtensionSystem : public TestExtensionSystem {
public:
explicit MockExtensionSystem(Profile* profile);
virtual ~MockExtensionSystem();
virtual EventRouter* event_router() OVERRIDE;
virtual ExtensionService* extension_service() OVERRIDE;
private:
scoped_ptr<EventRouter> event_router_;
MockExtensionService extension_service_;
DISALLOW_COPY_AND_ASSIGN(MockExtensionSystem);
};
// A Profile which returns an ExtensionService with enough functionality for
// the tests.
class MockProfile : public TestingProfile {
public:
explicit MockProfile(const FilePath& file_path);
virtual ~MockProfile();
};
// SettingsStorageFactory which acts as a wrapper for other factories.
class ScopedSettingsStorageFactory : public SettingsStorageFactory {
public:
ScopedSettingsStorageFactory();
explicit ScopedSettingsStorageFactory(
const scoped_refptr<SettingsStorageFactory>& delegate);
// Sets the delegate factory (equivalent to scoped_ptr::reset).
void Reset(const scoped_refptr<SettingsStorageFactory>& delegate);
// SettingsStorageFactory implementation.
virtual ValueStore* Create(const FilePath& base_path,
const std::string& extension_id) OVERRIDE;
private:
// SettingsStorageFactory is refcounted.
virtual ~ScopedSettingsStorageFactory();
scoped_refptr<SettingsStorageFactory> delegate_;
};
} // namespace settings_test_util
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SETTINGS_TEST_UTIL_H_
This diff is collapsed.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_STORAGE_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_STORAGE_API_H_
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "chrome/browser/extensions/api/storage/settings_namespace.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/value_store/value_store.h"
namespace extensions {
// Superclass of all settings functions.
class SettingsFunction : public AsyncExtensionFunction {
protected:
SettingsFunction();
virtual ~SettingsFunction();
// ExtensionFunction:
virtual bool ShouldSkipQuotaLimiting() const OVERRIDE;
virtual bool RunImpl() OVERRIDE;
// Extension settings function implementations should do their work here.
// The SettingsFrontend makes sure this is posted to the appropriate thread.
// Implementations should fill in args themselves, though (like RunImpl)
// may return false to imply failure.
virtual bool RunWithStorage(ValueStore* storage) = 0;
// Sets error_ or result_ depending on the value of a storage ReadResult, and
// returns whether the result implies success (i.e. !error).
bool UseReadResult(ValueStore::ReadResult result);
// Sets error_ depending on the value of a storage WriteResult, sends a
// change notification if needed, and returns whether the result implies
// success (i.e. !error).
bool UseWriteResult(ValueStore::WriteResult result);
private:
// Called via PostTask from RunImpl. Calls RunWithStorage and then
// SendResponse with its success value.
void AsyncRunWithStorage(ValueStore* storage);
// The settings namespace the call was for. For example, SYNC if the API
// call was chrome.settings.experimental.sync..., LOCAL if .local, etc.
settings_namespace::Namespace settings_namespace_;
// Observers, cached so that it's only grabbed from the UI thread.
scoped_refptr<SettingsObserverList> observers_;
};
class StorageGetFunction : public SettingsFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("storage.get");
protected:
virtual ~StorageGetFunction() {}
// SettingsFunction:
virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
};
class StorageSetFunction : public SettingsFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("storage.set");
protected:
virtual ~StorageSetFunction() {}
// SettingsFunction:
virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
// ExtensionFunction:
virtual void GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const OVERRIDE;
};
class StorageRemoveFunction : public SettingsFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("storage.remove");
protected:
virtual ~StorageRemoveFunction() {}
// SettingsFunction:
virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
// ExtensionFunction:
virtual void GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const OVERRIDE;
};
class StorageClearFunction : public SettingsFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("storage.clear");
protected:
virtual ~StorageClearFunction() {}
// SettingsFunction:
virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
// ExtensionFunction:
virtual void GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const OVERRIDE;
};
class StorageGetBytesInUseFunction : public SettingsFunction {
public:
DECLARE_EXTENSION_FUNCTION_NAME("storage.getBytesInUse");
protected:
virtual ~StorageGetBytesInUseFunction() {}
// SettingsFunction:
virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_STORAGE_API_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_STORAGE_SYNC_OR_LOCAL_VALUE_STORE_CACHE_H_
#define CHROME_BROWSER_EXTENSIONS_API_STORAGE_SYNC_OR_LOCAL_VALUE_STORE_CACHE_H_
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/storage/settings_observer.h"
#include "chrome/browser/extensions/api/storage/settings_storage_quota_enforcer.h"
#include "chrome/browser/extensions/api/storage/value_store_cache.h"
class FilePath;
namespace extensions {
class SettingsBackend;
class SettingsStorageFactory;
// ValueStoreCache for the LOCAL and SYNC namespaces. It owns a backend for
// apps and another for extensions. Each backend takes care of persistence and
// syncing.
class SyncOrLocalValueStoreCache : public ValueStoreCache {
public:
SyncOrLocalValueStoreCache(
settings_namespace::Namespace settings_namespace,
const scoped_refptr<SettingsStorageFactory>& factory,
const SettingsStorageQuotaEnforcer::Limits& quota,
const scoped_refptr<SettingsObserverList>& observers,
const FilePath& profile_path);
virtual ~SyncOrLocalValueStoreCache();
SettingsBackend* GetAppBackend() const;
SettingsBackend* GetExtensionBackend() const;
// ValueStoreCache implementation:
virtual void RunWithValueStoreForExtension(
const StorageCallback& callback,
scoped_refptr<const Extension> extension) OVERRIDE;
virtual void DeleteStorageSoon(const std::string& extension_id) OVERRIDE;
private:
void InitOnFileThread(const scoped_refptr<SettingsStorageFactory>& factory,
const SettingsStorageQuotaEnforcer::Limits& quota,
const scoped_refptr<SettingsObserverList>& observers,
const FilePath& profile_path);
settings_namespace::Namespace settings_namespace_;
scoped_ptr<SettingsBackend> app_backend_;
scoped_ptr<SettingsBackend> extension_backend_;
DISALLOW_COPY_AND_ASSIGN(SyncOrLocalValueStoreCache);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STORAGE_SYNC_OR_LOCAL_VALUE_STORE_CACHE_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/storage/value_store_cache.h"
namespace extensions {
ValueStoreCache::~ValueStoreCache() {}
void ValueStoreCache::ShutdownOnUI() {}
} // namespace extensions
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment