summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-16 05:00:20 +0000
committerkalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-16 05:00:20 +0000
commit4bbe629b332d548c48bb6474602a7ed438d8226b (patch)
tree145584b855760afa677da5d49a7aee7c03305c06
parentfb16d7f9b150ad6893e1db386707252b0dbdeab2 (diff)
downloadchromium_src-4bbe629b332d548c48bb6474602a7ed438d8226b.zip
chromium_src-4bbe629b332d548c48bb6474602a7ed438d8226b.tar.gz
chromium_src-4bbe629b332d548c48bb6474602a7ed438d8226b.tar.bz2
Add support for enforcing a quota on extension settings.
BUG=96709 TEST=unit test added Review URL: http://codereview.chromium.org/7908007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101456 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/extension_settings_quota_unittest.cc462
-rw-r--r--chrome/browser/extensions/extension_settings_storage_quota_enforcer.cc168
-rw-r--r--chrome/browser/extensions/extension_settings_storage_quota_enforcer.h55
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
5 files changed, 688 insertions, 0 deletions
diff --git a/chrome/browser/extensions/extension_settings_quota_unittest.cc b/chrome/browser/extensions/extension_settings_quota_unittest.cc
new file mode 100644
index 0000000..7329b6c
--- /dev/null
+++ b/chrome/browser/extensions/extension_settings_quota_unittest.cc
@@ -0,0 +1,462 @@
+// 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/json/json_writer.h"
+#include "base/values.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/extension_settings.h"
+#include "chrome/browser/extensions/extension_settings_storage_cache.h"
+#include "chrome/browser/extensions/extension_settings_storage_quota_enforcer.h"
+#include "chrome/browser/extensions/extension_settings_noop_storage.h"
+
+class ExtensionSettingsQuotaUnittest : public testing::Test {
+ public:
+ ExtensionSettingsQuotaUnittest()
+ : byte_value_1_(Value::CreateIntegerValue(1)),
+ byte_value_16_(Value::CreateStringValue("sixteen bytes.")),
+ byte_value_256_(new ListValue()),
+ delegate_(
+ new ExtensionSettingsStorageCache(
+ new ExtensionSettingsNoopStorage())) {
+ for (int i = 1; i < 89; ++i) {
+ byte_value_256_->Append(Value::CreateIntegerValue(i));
+ }
+ ValidateByteValues();
+ }
+
+ void ValidateByteValues() {
+ std::string validate_sizes;
+ base::JSONWriter::Write(byte_value_1_.get(), false, &validate_sizes);
+ ASSERT_EQ(1u, validate_sizes.size());
+ base::JSONWriter::Write(byte_value_16_.get(), false, &validate_sizes);
+ ASSERT_EQ(16u, validate_sizes.size());
+ base::JSONWriter::Write(byte_value_256_.get(), false, &validate_sizes);
+ ASSERT_EQ(256u, validate_sizes.size());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ ASSERT_TRUE(storage_.get() != NULL);
+ }
+
+ protected:
+ // Creates |storage_|. Must only be called once.
+ void CreateStorage(size_t quota_bytes, size_t max_keys) {
+ ASSERT_TRUE(storage_.get() == NULL);
+ storage_.reset(
+ new ExtensionSettingsStorageQuotaEnforcer(
+ quota_bytes, max_keys, delegate_));
+ }
+
+ // Returns whether the settings in |storage_| and |delegate_| are the same as
+ // |settings|.
+ bool SettingsEqual(const DictionaryValue& settings) {
+ return settings.Equals(storage_->Get().GetSettings()) &&
+ settings.Equals(delegate_->Get().GetSettings());
+ }
+
+ // Values with different serialized sizes.
+ scoped_ptr<Value> byte_value_1_;
+ scoped_ptr<Value> byte_value_16_;
+ scoped_ptr<ListValue> byte_value_256_;
+
+ // Quota enforcing storage area being tested.
+ scoped_ptr<ExtensionSettingsStorageQuotaEnforcer> storage_;
+
+ // In-memory storage area being delegated to. Always owned by |storage_|.
+ ExtensionSettingsStorageCache* delegate_;
+};
+
+TEST_F(ExtensionSettingsQuotaUnittest, ZeroQuotaBytes) {
+ DictionaryValue empty;
+ CreateStorage(0, UINT_MAX);
+
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_FALSE(storage_->Remove("a").HasError());
+ EXPECT_FALSE(storage_->Remove("b").HasError());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, KeySizeTakenIntoAccount) {
+ DictionaryValue empty;
+ CreateStorage(8u, UINT_MAX);
+ EXPECT_TRUE(storage_->Set("Really long key", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, SmallByteQuota) {
+ DictionaryValue settings;
+ CreateStorage(8u, UINT_MAX);
+
+ EXPECT_FALSE(storage_->Set("a", *byte_value_1_).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("b", *byte_value_16_).HasError());
+ EXPECT_TRUE(storage_->Set("c", *byte_value_256_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, MediumByteQuota) {
+ DictionaryValue settings;
+ CreateStorage(40, UINT_MAX);
+
+ DictionaryValue to_set;
+ to_set.Set("a", byte_value_1_->DeepCopy());
+ to_set.Set("b", byte_value_16_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ settings.Set("b", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able to set value to other under-quota value.
+ to_set.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ settings.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("c", *byte_value_256_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, ZeroMaxKeys) {
+ DictionaryValue empty;
+ CreateStorage(UINT_MAX, 0);
+
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_FALSE(storage_->Remove("a").HasError());
+ EXPECT_FALSE(storage_->Remove("b").HasError());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, SmallMaxKeys) {
+ DictionaryValue settings;
+ CreateStorage(UINT_MAX, 1);
+
+ EXPECT_FALSE(storage_->Set("a", *byte_value_1_).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able to set existing key to other value without going over quota.
+ EXPECT_FALSE(storage_->Set("a", *byte_value_16_).HasError());
+ settings.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("b", *byte_value_16_).HasError());
+ EXPECT_TRUE(storage_->Set("c", *byte_value_256_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, MediumMaxKeys) {
+ DictionaryValue settings;
+ CreateStorage(UINT_MAX, 2);
+
+ DictionaryValue to_set;
+ to_set.Set("a", byte_value_1_->DeepCopy());
+ to_set.Set("b", byte_value_16_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ settings.Set("b", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able to set existing keys to other values without going over
+ // quota.
+ to_set.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ settings.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("c", *byte_value_256_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, RemovingExistingSettings) {
+ DictionaryValue settings;
+ CreateStorage(266, 2);
+
+ storage_->Set("b", *byte_value_16_);
+ settings.Set("b", byte_value_16_->DeepCopy());
+ // Not enough quota.
+ storage_->Set("c", *byte_value_256_);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Try again with "b" removed, enough quota.
+ EXPECT_FALSE(storage_->Remove("b").HasError());
+ settings.Remove("b", NULL);
+ EXPECT_FALSE(storage_->Set("c", *byte_value_256_).HasError());
+ settings.Set("c", byte_value_256_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Enough byte quota but max keys not high enough.
+ EXPECT_FALSE(storage_->Set("a", *byte_value_1_).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("b", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Back under max keys.
+ EXPECT_FALSE(storage_->Remove("a").HasError());
+ settings.Remove("a", NULL);
+ EXPECT_FALSE(storage_->Set("b", *byte_value_1_).HasError());
+ settings.Set("b", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, RemovingNonexistentSettings) {
+ DictionaryValue settings;
+ CreateStorage(36, 3);
+
+ // Max out bytes.
+ DictionaryValue to_set;
+ to_set.Set("b1", byte_value_16_->DeepCopy());
+ to_set.Set("b2", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(to_set.Equals(storage_->Set(to_set).GetSettings()));
+ settings.Set("b1", byte_value_16_->DeepCopy());
+ settings.Set("b2", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Remove some settings that don't exist.
+ std::vector<std::string> to_remove;
+ to_remove.push_back("a1");
+ to_remove.push_back("a2");
+ EXPECT_FALSE(storage_->Remove(to_remove).HasError());
+ EXPECT_FALSE(storage_->Remove("b").HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Still no quota.
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Max out key count.
+ to_set.Clear();
+ to_set.Set("b1", byte_value_1_->DeepCopy());
+ to_set.Set("b2", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(to_set.Equals(storage_->Set(to_set).GetSettings()));
+ settings.Set("b1", byte_value_1_->DeepCopy());
+ settings.Set("b2", byte_value_1_->DeepCopy());
+ storage_->Set("b3", *byte_value_1_);
+ settings.Set("b3", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Remove some settings that don't exist.
+ to_remove.clear();
+ to_remove.push_back("a1");
+ to_remove.push_back("a2");
+ EXPECT_FALSE(storage_->Remove(to_remove).HasError());
+ EXPECT_FALSE(storage_->Remove("b").HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Still no quota.
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, Clear) {
+ DictionaryValue settings;
+ CreateStorage(40, 5);
+
+ // Test running out of byte quota.
+ DictionaryValue to_set;
+ to_set.Set("a", byte_value_16_->DeepCopy());
+ to_set.Set("b", byte_value_16_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ EXPECT_TRUE(storage_->Set("c", *byte_value_16_).HasError());
+
+ EXPECT_FALSE(storage_->Clear().HasError());
+
+ // (repeat)
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ EXPECT_TRUE(storage_->Set("c", *byte_value_16_).HasError());
+
+ // Test reaching max keys.
+ storage_->Clear();
+ to_set.Clear();
+ to_set.Set("a", byte_value_1_->DeepCopy());
+ to_set.Set("b", byte_value_1_->DeepCopy());
+ to_set.Set("c", byte_value_1_->DeepCopy());
+ to_set.Set("d", byte_value_1_->DeepCopy());
+ to_set.Set("e", byte_value_1_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ EXPECT_TRUE(storage_->Set("f", *byte_value_1_).HasError());
+
+ storage_->Clear();
+
+ // (repeat)
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ EXPECT_TRUE(storage_->Set("f", *byte_value_1_).HasError());
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, ChangingUsedBytesWithSet) {
+ DictionaryValue settings;
+ CreateStorage(20, UINT_MAX);
+
+ // Change a setting to make it go over quota.
+ storage_->Set("a", *byte_value_16_);
+ settings.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("a", *byte_value_256_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Change a setting to reduce usage and room for another setting.
+ EXPECT_TRUE(storage_->Set("foobar", *byte_value_1_).HasError());
+ storage_->Set("a", *byte_value_1_);
+ settings.Set("a", byte_value_1_->DeepCopy());
+
+ EXPECT_FALSE(storage_->Set("foobar", *byte_value_1_).HasError());
+ settings.Set("foobar", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, SetsOnlyEntirelyCompletedWithByteQuota) {
+ DictionaryValue settings;
+ CreateStorage(40, UINT_MAX);
+
+ storage_->Set("a", *byte_value_16_);
+ settings.Set("a", byte_value_16_->DeepCopy());
+
+ // The entire change is over quota.
+ DictionaryValue to_set;
+ to_set.Set("b", byte_value_16_->DeepCopy());
+ to_set.Set("c", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(storage_->Set(to_set).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // The entire change is over quota, but quota reduced in existing key.
+ to_set.Set("a", byte_value_1_->DeepCopy());
+ EXPECT_FALSE(storage_->Set(to_set).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ settings.Set("b", byte_value_16_->DeepCopy());
+ settings.Set("c", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, SetsOnlyEntireCompletedWithMaxKeys) {
+ DictionaryValue settings;
+ CreateStorage(UINT_MAX, 2);
+
+ storage_->Set("a", *byte_value_1_);
+ settings.Set("a", byte_value_1_->DeepCopy());
+
+ DictionaryValue to_set;
+ to_set.Set("b", byte_value_16_->DeepCopy());
+ to_set.Set("c", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(storage_->Set(to_set).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, WithInitialDataAndByteQuota) {
+ DictionaryValue settings;
+ delegate_->Set("a", *byte_value_256_);
+ settings.Set("a", byte_value_256_->DeepCopy());
+
+ CreateStorage(280, UINT_MAX);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Add some data.
+ EXPECT_FALSE(storage_->Set("b", *byte_value_16_).HasError());
+ settings.Set("b", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Not enough quota.
+ EXPECT_TRUE(storage_->Set("c", *byte_value_16_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Reduce usage of original setting so that "c" can fit.
+ EXPECT_FALSE(storage_->Set("a", *byte_value_16_).HasError());
+ settings.Set("a", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set("c", *byte_value_16_).HasError());
+ settings.Set("c", byte_value_16_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Remove to free up some more data.
+ EXPECT_TRUE(storage_->Set("d", *byte_value_256_).HasError());
+
+ std::vector<std::string> to_remove;
+ to_remove.push_back("a");
+ to_remove.push_back("b");
+ storage_->Remove(to_remove);
+ settings.Remove("a", NULL);
+ settings.Remove("b", NULL);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set("d", *byte_value_256_).HasError());
+ settings.Set("d", byte_value_256_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, WithInitialDataAndMaxKeys) {
+ DictionaryValue settings;
+ delegate_->Set("a", *byte_value_1_);
+ settings.Set("a", byte_value_1_->DeepCopy());
+ CreateStorage(UINT_MAX, 2);
+
+ EXPECT_FALSE(storage_->Set("b", *byte_value_1_).HasError());
+ settings.Set("b", byte_value_1_->DeepCopy());
+
+ EXPECT_TRUE(storage_->Set("c", *byte_value_1_).HasError());
+
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, InitiallyOverByteQuota) {
+ DictionaryValue settings;
+ settings.Set("a", byte_value_16_->DeepCopy());
+ settings.Set("b", byte_value_16_->DeepCopy());
+ settings.Set("c", byte_value_16_->DeepCopy());
+ delegate_->Set(settings);
+
+ CreateStorage(40, UINT_MAX);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set("d", *byte_value_16_).HasError());
+
+ // Take under quota by reducing size of an existing setting
+ EXPECT_FALSE(storage_->Set("a", *byte_value_1_).HasError());
+ settings.Set("a", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able set another small setting.
+ EXPECT_FALSE(storage_->Set("d", *byte_value_1_).HasError());
+ settings.Set("d", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaUnittest, InitiallyOverMaxKeys) {
+ DictionaryValue settings;
+ settings.Set("a", byte_value_16_->DeepCopy());
+ settings.Set("b", byte_value_16_->DeepCopy());
+ settings.Set("c", byte_value_16_->DeepCopy());
+ delegate_->Set(settings);
+
+ CreateStorage(UINT_MAX, 2);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Can't set either an existing or new setting.
+ EXPECT_TRUE(storage_->Set("d", *byte_value_16_).HasError());
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able after removing 2.
+ storage_->Remove("a");
+ settings.Remove("a", NULL);
+ storage_->Remove("b");
+ settings.Remove("b", NULL);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set("e", *byte_value_1_).HasError());
+ settings.Set("e", byte_value_1_->DeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Still can't set any.
+ EXPECT_TRUE(storage_->Set("d", *byte_value_16_).HasError());
+ EXPECT_TRUE(storage_->Set("a", *byte_value_1_).HasError());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
diff --git a/chrome/browser/extensions/extension_settings_storage_quota_enforcer.cc b/chrome/browser/extensions/extension_settings_storage_quota_enforcer.cc
new file mode 100644
index 0000000..9f56a53
--- /dev/null
+++ b/chrome/browser/extensions/extension_settings_storage_quota_enforcer.cc
@@ -0,0 +1,168 @@
+// 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_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/task.h"
+
+namespace {
+
+const char* kExceededQuotaErrorMessage = "Quota exceeded";
+
+// 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, false, &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);
+}
+
+} // namespace
+
+ExtensionSettingsStorageQuotaEnforcer::ExtensionSettingsStorageQuotaEnforcer(
+ size_t quota_bytes, size_t max_keys, ExtensionSettingsStorage* delegate)
+ : quota_bytes_(quota_bytes),
+ max_keys_(max_keys),
+ delegate_(delegate),
+ used_total_(0) {
+ Result maybe_initial_settings = delegate->Get();
+ if (maybe_initial_settings.HasError()) {
+ LOG(WARNING) << "Failed to get initial settings for quota: " <<
+ maybe_initial_settings.GetError();
+ return;
+ }
+
+ DictionaryValue* initial_settings = maybe_initial_settings.GetSettings();
+ for (DictionaryValue::key_iterator it = initial_settings->begin_keys();
+ it != initial_settings->end_keys(); ++it) {
+ Value *value;
+ initial_settings->GetWithoutPathExpansion(*it, &value);
+ Allocate(*it, *value, &used_total_, &used_per_setting_);
+ }
+}
+
+ExtensionSettingsStorageQuotaEnforcer::~ExtensionSettingsStorageQuotaEnforcer(
+ ) {}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Get(
+ const std::string& key) {
+ return delegate_->Get(key);
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Get(
+ const std::vector<std::string>& keys) {
+ return delegate_->Get(keys);
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Get() {
+ return delegate_->Get();
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Set(
+ 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 (new_used_total > quota_bytes_ ||
+ new_used_per_setting.size() > max_keys_) {
+ return Result(kExceededQuotaErrorMessage);
+ }
+
+ Result result = delegate_->Set(key, value);
+ if (result.HasError()) {
+ return result;
+ }
+
+ used_total_ = new_used_total;
+ used_per_setting_.swap(new_used_per_setting);
+ return result;
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Set(
+ 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::key_iterator it = values.begin_keys();
+ it != values.end_keys(); ++it) {
+ Value* value;
+ values.GetWithoutPathExpansion(*it, &value);
+ Allocate(*it, *value, &new_used_total, &new_used_per_setting);
+ }
+
+ if (new_used_total > quota_bytes_ ||
+ new_used_per_setting.size() > max_keys_) {
+ return Result(kExceededQuotaErrorMessage);
+ }
+
+ Result result = delegate_->Set(values);
+ if (result.HasError()) {
+ return result;
+ }
+
+ used_total_ = new_used_total;
+ used_per_setting_ = new_used_per_setting;
+ return result;
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Remove(
+ const std::string& key) {
+ Result result = delegate_->Remove(key);
+ if (result.HasError()) {
+ return result;
+ }
+ Free(&used_total_, &used_per_setting_, key);
+ return result;
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Remove(
+ const std::vector<std::string>& keys) {
+ Result result = delegate_->Remove(keys);
+ if (result.HasError()) {
+ return result;
+ }
+
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ Free(&used_total_, &used_per_setting_, *it);
+ }
+ return result;
+}
+
+ExtensionSettingsStorage::Result ExtensionSettingsStorageQuotaEnforcer::Clear(
+ ) {
+ Result result = delegate_->Clear();
+ if (result.HasError()) {
+ return result;
+ }
+
+ while (!used_per_setting_.empty()) {
+ Free(&used_total_, &used_per_setting_, used_per_setting_.begin()->first);
+ }
+ return result;
+}
diff --git a/chrome/browser/extensions/extension_settings_storage_quota_enforcer.h b/chrome/browser/extensions/extension_settings_storage_quota_enforcer.h
new file mode 100644
index 0000000..beb498b
--- /dev/null
+++ b/chrome/browser/extensions/extension_settings_storage_quota_enforcer.h
@@ -0,0 +1,55 @@
+// 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_STORAGE_QUOTA_ENFORCER_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/extensions/extension_settings_storage.h"
+
+// Enforces a quota size in bytes and a maximum number of setting keys for a
+// delegate storage area.
+class ExtensionSettingsStorageQuotaEnforcer : public ExtensionSettingsStorage {
+ public:
+ ExtensionSettingsStorageQuotaEnforcer(
+ size_t quota_bytes,
+ size_t max_keys,
+ // Ownership taken.
+ ExtensionSettingsStorage* delegate);
+
+ virtual ~ExtensionSettingsStorageQuotaEnforcer();
+
+ // 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;
+
+ private:
+ // The storage quota in bytes.
+ size_t const quota_bytes_;
+
+ // The maximum number of settings keys allowed.
+ size_t const max_keys_;
+
+ // The delegate storage area.
+ ExtensionSettingsStorage* const delegate_;
+
+ // Total size of the settings currently being used. Includes both settings
+ // keys and their JSON-encoded values.
+ size_t used_total_;
+
+ // Map of key to size of that key, including the key itself.
+ std::map<std::string, size_t> used_per_setting_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionSettingsStorageQuotaEnforcer);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 596d842..f525762 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1085,6 +1085,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_storage_quota_enforcer.cc',
+ 'browser/extensions/extension_settings_storage_quota_enforcer.h',
'browser/extensions/extension_settings_leveldb_storage.cc',
'browser/extensions/extension_settings_leveldb_storage.h',
'browser/extensions/extension_service.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index b60b2fa..c0d8a8d 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2310,6 +2310,7 @@
'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',