summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 19:57:41 +0000
committerjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 19:57:41 +0000
commit3a4f74af6084db9d35b298f889d00557445e8bab (patch)
treed784934185d8a9aa82d728191375c4863ee3ab4b /extensions
parent2f018d6f8564359c10abf57999a787142713c631 (diff)
downloadchromium_src-3a4f74af6084db9d35b298f889d00557445e8bab.zip
chromium_src-3a4f74af6084db9d35b298f889d00557445e8bab.tar.gz
chromium_src-3a4f74af6084db9d35b298f889d00557445e8bab.tar.bz2
Move storage API interface description to src/extensions
The extensions local-storage API is moving into src/extensions so it can be used by app_shell. This finishes that move. * Move storage.json to extensions/common/api * Move storage functions into core_api namespace * Remove ExtensionService references from StorageApiUnittest BUG=348058 TEST=unit_tests *Storage* and browser_tests *Storage* Review URL: https://codereview.chromium.org/197883002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256901 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
-rw-r--r--extensions/browser/DEPS9
-rw-r--r--extensions/browser/api/storage/local_value_store_cache.cc6
-rw-r--r--extensions/browser/api/storage/settings_frontend.cc4
-rw-r--r--extensions/browser/api/storage/storage_api.cc304
-rw-r--r--extensions/browser/api/storage/storage_api.h143
-rw-r--r--extensions/browser/api/storage/storage_api_unittest.cc117
-rw-r--r--extensions/common/api/api.gyp4
-rw-r--r--extensions/common/api/storage.json219
-rw-r--r--extensions/extensions.gyp2
9 files changed, 795 insertions, 13 deletions
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS
index f6f8c0b..5a3b8bd 100644
--- a/extensions/browser/DEPS
+++ b/extensions/browser/DEPS
@@ -32,17 +32,10 @@ specific_include_rules = {
"(.*test|.*test_util)\.(cc|h)": [
# Temporarily allowed testing includes. See above.
# TODO(jamescook): Remove these. http://crbug.com/162530
+ "+chrome/browser/extensions/extension_api_unittest.h",
"+chrome/browser/extensions/extension_service_unittest.h",
"+chrome/browser/extensions/test_extension_system.h",
"+chrome/common/chrome_paths.h",
"+chrome/test/base/testing_profile.h",
],
- # TODO(jamescook): Eliminate these after moving storage.idl to src/extensions.
- # http://crbug.com/348058
- "local_value_store_cache\.cc": [
- "+chrome/common/extensions/api/storage.h"
- ],
- "settings_frontend\.cc": [
- "+chrome/common/extensions/api/storage.h"
- ]
}
diff --git a/extensions/browser/api/storage/local_value_store_cache.cc b/extensions/browser/api/storage/local_value_store_cache.cc
index 7c7ce58..6d05ca2 100644
--- a/extensions/browser/api/storage/local_value_store_cache.cc
+++ b/extensions/browser/api/storage/local_value_store_cache.cc
@@ -9,12 +9,12 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
-#include "chrome/common/extensions/api/storage.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/storage/settings_storage_factory.h"
#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
#include "extensions/browser/api/storage/weak_unlimited_settings_storage.h"
#include "extensions/browser/value_store/value_store.h"
+#include "extensions/common/api/storage.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/api_permission.h"
@@ -26,10 +26,10 @@ namespace extensions {
namespace {
// Returns the quota limit for local storage, taken from the schema in
-// chrome/common/extensions/api/storage.json.
+// extensions/common/api/storage.json.
SettingsStorageQuotaEnforcer::Limits GetLocalQuotaLimits() {
SettingsStorageQuotaEnforcer::Limits limits = {
- static_cast<size_t>(api::storage::local::QUOTA_BYTES),
+ static_cast<size_t>(core_api::storage::local::QUOTA_BYTES),
std::numeric_limits<size_t>::max(),
std::numeric_limits<size_t>::max()
};
diff --git a/extensions/browser/api/storage/settings_frontend.cc b/extensions/browser/api/storage/settings_frontend.cc
index 6fa3337..87dc8e9 100644
--- a/extensions/browser/api/storage/settings_frontend.cc
+++ b/extensions/browser/api/storage/settings_frontend.cc
@@ -9,7 +9,6 @@
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
-#include "chrome/common/extensions/api/storage.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/extensions_api_client.h"
@@ -18,6 +17,7 @@
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
+#include "extensions/common/api/storage.h"
using content::BrowserContext;
using content::BrowserThread;
@@ -48,7 +48,7 @@ class DefaultObserver : public SettingsObserver {
args->Append(new base::StringValue(settings_namespace::ToString(
settings_namespace)));
scoped_ptr<Event> event(new Event(
- api::storage::OnChanged::kEventName, args.Pass()));
+ core_api::storage::OnChanged::kEventName, args.Pass()));
ExtensionSystem::Get(browser_context_)->event_router()->
DispatchEventToExtension(extension_id, event.Pass());
}
diff --git a/extensions/browser/api/storage/storage_api.cc b/extensions/browser/api/storage/storage_api.cc
new file mode 100644
index 0000000..939a75a
--- /dev/null
+++ b/extensions/browser/api/storage/storage_api.cc
@@ -0,0 +1,304 @@
+// Copyright 2014 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 "extensions/browser/api/storage/storage_api.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/storage/settings_frontend.h"
+#include "extensions/browser/quota_service.h"
+#include "extensions/common/api/storage.h"
+
+namespace extensions {
+
+using content::BrowserThread;
+
+// SettingsFunction
+
+SettingsFunction::SettingsFunction()
+ : settings_namespace_(settings_namespace::INVALID),
+ tried_restoring_storage_(false) {}
+
+SettingsFunction::~SettingsFunction() {}
+
+bool SettingsFunction::ShouldSkipQuotaLimiting() const {
+ // Only apply quota if this is for sync storage.
+ std::string settings_namespace_string;
+ if (!args_->GetString(0, &settings_namespace_string)) {
+ // This should be EXTENSION_FUNCTION_VALIDATE(false) but there is no way
+ // to signify that from this function. It will be caught in RunImpl().
+ return false;
+ }
+ return settings_namespace_string != "sync";
+}
+
+bool SettingsFunction::RunImpl() {
+ std::string settings_namespace_string;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &settings_namespace_string));
+ args_->Remove(0, NULL);
+ settings_namespace_ =
+ settings_namespace::FromString(settings_namespace_string);
+ EXTENSION_FUNCTION_VALIDATE(
+ settings_namespace_ != settings_namespace::INVALID);
+
+ SettingsFrontend* frontend = SettingsFrontend::Get(browser_context());
+ if (!frontend->IsStorageEnabled(settings_namespace_)) {
+ error_ = base::StringPrintf(
+ "\"%s\" is not available in this instance of Chrome",
+ settings_namespace_string.c_str());
+ return false;
+ }
+
+ observers_ = frontend->GetObservers();
+ frontend->RunWithStorage(
+ GetExtension(),
+ settings_namespace_,
+ base::Bind(&SettingsFunction::AsyncRunWithStorage, this));
+ return true;
+}
+
+void SettingsFunction::AsyncRunWithStorage(ValueStore* storage) {
+ bool success = RunWithStorage(storage);
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&SettingsFunction::SendResponse, this, success));
+}
+
+bool SettingsFunction::UseReadResult(ValueStore::ReadResult result,
+ ValueStore* storage) {
+ if (result->HasError())
+ return HandleError(result->error(), storage);
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->Swap(&result->settings());
+ SetResult(dict);
+ return true;
+}
+
+bool SettingsFunction::UseWriteResult(ValueStore::WriteResult result,
+ ValueStore* storage) {
+ if (result->HasError())
+ return HandleError(result->error(), storage);
+
+ if (!result->changes().empty()) {
+ observers_->Notify(
+ &SettingsObserver::OnSettingsChanged,
+ extension_id(),
+ settings_namespace_,
+ ValueStoreChange::ToJson(result->changes()));
+ }
+
+ return true;
+}
+
+bool SettingsFunction::HandleError(const ValueStore::Error& error,
+ ValueStore* storage) {
+ // If the method failed due to corruption, and we haven't tried to fix it, we
+ // can try to restore the storage and re-run it. Otherwise, the method has
+ // failed.
+ if (error.code == ValueStore::CORRUPTION && !tried_restoring_storage_) {
+ tried_restoring_storage_ = true;
+
+ // If the corruption is on a particular key, try to restore that key and
+ // re-run.
+ if (error.key.get() && storage->RestoreKey(*error.key))
+ return RunWithStorage(storage);
+
+ // If the full database is corrupted, try to restore the whole thing and
+ // re-run.
+ if (storage->Restore())
+ return RunWithStorage(storage);
+ }
+
+ error_ = error.message;
+ return false;
+}
+
+// Concrete settings functions
+
+namespace {
+
+// Adds all StringValues from a ListValue to a vector of strings.
+void AddAllStringValues(const base::ListValue& from,
+ std::vector<std::string>* to) {
+ DCHECK(to->empty());
+ std::string as_string;
+ for (base::ListValue::const_iterator it = from.begin();
+ it != from.end(); ++it) {
+ if ((*it)->GetAsString(&as_string)) {
+ to->push_back(as_string);
+ }
+ }
+}
+
+// Gets the keys of a DictionaryValue.
+std::vector<std::string> GetKeys(const base::DictionaryValue& dict) {
+ std::vector<std::string> keys;
+ for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
+ keys.push_back(it.key());
+ }
+ return keys;
+}
+
+// Creates quota heuristics for settings modification.
+void GetModificationQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) {
+ QuotaLimitHeuristic::Config longLimitConfig = {
+ // See storage.json for current value.
+ core_api::storage::sync::MAX_WRITE_OPERATIONS_PER_HOUR,
+ base::TimeDelta::FromHours(1)
+ };
+ heuristics->push_back(new QuotaService::TimedLimit(
+ longLimitConfig,
+ new QuotaLimitHeuristic::SingletonBucketMapper(),
+ "MAX_WRITE_OPERATIONS_PER_HOUR"));
+
+ // A max of 10 operations per minute, sustained over 10 minutes.
+ QuotaLimitHeuristic::Config shortLimitConfig = {
+ // See storage.json for current value.
+ core_api::storage::sync::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE,
+ base::TimeDelta::FromMinutes(1)
+ };
+ heuristics->push_back(new QuotaService::SustainedLimit(
+ base::TimeDelta::FromMinutes(10),
+ shortLimitConfig,
+ new QuotaLimitHeuristic::SingletonBucketMapper(),
+ "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
+};
+
+} // namespace
+
+bool StorageStorageAreaGetFunction::RunWithStorage(ValueStore* storage) {
+ base::Value* input = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &input));
+
+ switch (input->GetType()) {
+ case base::Value::TYPE_NULL:
+ return UseReadResult(storage->Get(), storage);
+
+ case base::Value::TYPE_STRING: {
+ std::string as_string;
+ input->GetAsString(&as_string);
+ return UseReadResult(storage->Get(as_string), storage);
+ }
+
+ case base::Value::TYPE_LIST: {
+ std::vector<std::string> as_string_list;
+ AddAllStringValues(*static_cast<base::ListValue*>(input),
+ &as_string_list);
+ return UseReadResult(storage->Get(as_string_list), storage);
+ }
+
+ case base::Value::TYPE_DICTIONARY: {
+ base::DictionaryValue* as_dict =
+ static_cast<base::DictionaryValue*>(input);
+ ValueStore::ReadResult result = storage->Get(GetKeys(*as_dict));
+ if (result->HasError()) {
+ return UseReadResult(result.Pass(), storage);
+ }
+
+ base::DictionaryValue* with_default_values = as_dict->DeepCopy();
+ with_default_values->MergeDictionary(&result->settings());
+ return UseReadResult(
+ ValueStore::MakeReadResult(make_scoped_ptr(with_default_values)),
+ storage);
+ }
+
+ default:
+ EXTENSION_FUNCTION_VALIDATE(false);
+ return false;
+ }
+}
+
+bool StorageStorageAreaGetBytesInUseFunction::RunWithStorage(
+ ValueStore* storage) {
+ base::Value* input = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &input));
+
+ size_t bytes_in_use = 0;
+
+ switch (input->GetType()) {
+ case base::Value::TYPE_NULL:
+ bytes_in_use = storage->GetBytesInUse();
+ break;
+
+ case base::Value::TYPE_STRING: {
+ std::string as_string;
+ input->GetAsString(&as_string);
+ bytes_in_use = storage->GetBytesInUse(as_string);
+ break;
+ }
+
+ case base::Value::TYPE_LIST: {
+ std::vector<std::string> as_string_list;
+ AddAllStringValues(*static_cast<base::ListValue*>(input),
+ &as_string_list);
+ bytes_in_use = storage->GetBytesInUse(as_string_list);
+ break;
+ }
+
+ default:
+ EXTENSION_FUNCTION_VALIDATE(false);
+ return false;
+ }
+
+ SetResult(new base::FundamentalValue(static_cast<int>(bytes_in_use)));
+ return true;
+}
+
+bool StorageStorageAreaSetFunction::RunWithStorage(ValueStore* storage) {
+ base::DictionaryValue* input = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &input));
+ return UseWriteResult(storage->Set(ValueStore::DEFAULTS, *input), storage);
+}
+
+void StorageStorageAreaSetFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ GetModificationQuotaLimitHeuristics(heuristics);
+}
+
+bool StorageStorageAreaRemoveFunction::RunWithStorage(ValueStore* storage) {
+ base::Value* input = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &input));
+
+ switch (input->GetType()) {
+ case base::Value::TYPE_STRING: {
+ std::string as_string;
+ input->GetAsString(&as_string);
+ return UseWriteResult(storage->Remove(as_string), storage);
+ }
+
+ case base::Value::TYPE_LIST: {
+ std::vector<std::string> as_string_list;
+ AddAllStringValues(*static_cast<base::ListValue*>(input),
+ &as_string_list);
+ return UseWriteResult(storage->Remove(as_string_list), storage);
+ }
+
+ default:
+ EXTENSION_FUNCTION_VALIDATE(false);
+ return false;
+ };
+}
+
+void StorageStorageAreaRemoveFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ GetModificationQuotaLimitHeuristics(heuristics);
+}
+
+bool StorageStorageAreaClearFunction::RunWithStorage(ValueStore* storage) {
+ return UseWriteResult(storage->Clear(), storage);
+}
+
+void StorageStorageAreaClearFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ GetModificationQuotaLimitHeuristics(heuristics);
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/storage/storage_api.h b/extensions/browser/api/storage/storage_api.h
new file mode 100644
index 0000000..f573235
--- /dev/null
+++ b/extensions/browser/api/storage/storage_api.h
@@ -0,0 +1,143 @@
+// Copyright 2014 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 EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+#include "extensions/browser/api/storage/settings_observer.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace extensions {
+
+// Superclass of all settings functions.
+class SettingsFunction : public UIThreadExtensionFunction {
+ 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;
+
+ // Handles the |result| of a read function.
+ // - If the result succeeded, this will set |result_| and return.
+ // - If |result| failed with a ValueStore::CORRUPTION error, this will call
+ // RestoreStorageAndRetry(), and return that result.
+ // - If the |result| failed with a different error, this will set |error_|
+ // and return.
+ bool UseReadResult(ValueStore::ReadResult result, ValueStore* storage);
+
+ // Handles the |result| of a write function.
+ // - If the result succeeded, this will set |result_| and return.
+ // - If |result| failed with a ValueStore::CORRUPTION error, this will call
+ // RestoreStorageAndRetry(), and return that result.
+ // - If the |result| failed with a different error, this will set |error_|
+ // and return.
+ // This will also send out a change notification, if appropriate.
+ bool UseWriteResult(ValueStore::WriteResult result, ValueStore* storage);
+
+ private:
+ // Called via PostTask from RunImpl. Calls RunWithStorage and then
+ // SendResponse with its success value.
+ void AsyncRunWithStorage(ValueStore* storage);
+
+ // Called if we encounter a ValueStore error. If the error is due to
+ // corruption, tries to restore the ValueStore and re-run the API function.
+ // If the storage cannot be restored or was due to some other error, then sets
+ // error and returns. This also sets the |tried_restoring_storage_| flag to
+ // ensure we don't enter a loop.
+ bool HandleError(const ValueStore::Error& error, 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_;
+
+ // A flag indicating whether or not we have tried to restore storage. We
+ // should only ever try once (per API call) in order to avoid entering a loop.
+ bool tried_restoring_storage_;
+
+ // Observers, cached so that it's only grabbed from the UI thread.
+ scoped_refptr<SettingsObserverList> observers_;
+};
+
+class StorageStorageAreaGetFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.get", STORAGE_GET)
+
+ protected:
+ virtual ~StorageStorageAreaGetFunction() {}
+
+ // SettingsFunction:
+ virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
+};
+
+class StorageStorageAreaSetFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.set", STORAGE_SET)
+
+ protected:
+ virtual ~StorageStorageAreaSetFunction() {}
+
+ // SettingsFunction:
+ virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
+
+ // ExtensionFunction:
+ virtual void GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const OVERRIDE;
+};
+
+class StorageStorageAreaRemoveFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.remove", STORAGE_REMOVE)
+
+ protected:
+ virtual ~StorageStorageAreaRemoveFunction() {}
+
+ // SettingsFunction:
+ virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
+
+ // ExtensionFunction:
+ virtual void GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const OVERRIDE;
+};
+
+class StorageStorageAreaClearFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.clear", STORAGE_CLEAR)
+
+ protected:
+ virtual ~StorageStorageAreaClearFunction() {}
+
+ // SettingsFunction:
+ virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
+
+ // ExtensionFunction:
+ virtual void GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const OVERRIDE;
+};
+
+class StorageStorageAreaGetBytesInUseFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.getBytesInUse", STORAGE_GETBYTESINUSE)
+
+ protected:
+ virtual ~StorageStorageAreaGetBytesInUseFunction() {}
+
+ // SettingsFunction:
+ virtual bool RunWithStorage(ValueStore* storage) OVERRIDE;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_
diff --git a/extensions/browser/api/storage/storage_api_unittest.cc b/extensions/browser/api/storage/storage_api_unittest.cc
new file mode 100644
index 0000000..2fe5269
--- /dev/null
+++ b/extensions/browser/api/storage/storage_api_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/extensions/extension_api_unittest.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "extensions/browser/api/storage/leveldb_settings_storage_factory.h"
+#include "extensions/browser/api/storage/settings_frontend.h"
+#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
+#include "extensions/browser/api/storage/settings_test_util.h"
+#include "extensions/browser/api/storage/storage_api.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "extensions/common/id_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/test_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace extensions {
+
+namespace {
+
+// Caller owns the returned object.
+KeyedService* CreateSettingsFrontendForTesting(
+ content::BrowserContext* context) {
+ return SettingsFrontend::CreateForTesting(new LeveldbSettingsStorageFactory(),
+ context);
+}
+
+} // namespace
+
+class StorageApiUnittest : public ExtensionApiUnittest {
+ public:
+ virtual void SetUp() OVERRIDE {
+ ExtensionApiUnittest::SetUp();
+ TestExtensionSystem* extension_system =
+ static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()));
+ // SettingsFrontend requires an EventRouter.
+ extension_system->SetEventRouter(scoped_ptr<EventRouter>(
+ new EventRouter(profile(), ExtensionPrefs::Get(profile()))));
+ }
+
+ protected:
+ // Runs the storage.set() API function with local storage.
+ void RunSetFunction(const std::string& key, const std::string& value) {
+ RunFunction(
+ new StorageStorageAreaSetFunction(),
+ base::StringPrintf(
+ "[\"local\", {\"%s\": \"%s\"}]", key.c_str(), value.c_str()));
+ }
+
+ // Runs the storage.get() API function with the local storage, and populates
+ // |value| with the string result.
+ testing::AssertionResult RunGetFunction(const std::string& key,
+ std::string* value) {
+ scoped_ptr<base::Value> result = RunFunctionAndReturnValue(
+ new StorageStorageAreaGetFunction(),
+ base::StringPrintf("[\"local\", \"%s\"]", key.c_str()));
+ base::DictionaryValue* dict = NULL;
+ if (!result->GetAsDictionary(&dict))
+ return testing::AssertionFailure() << result << " was not a dictionary.";
+ if (!dict->GetString(key, value)) {
+ return testing::AssertionFailure() << " could not retrieve a string from"
+ << dict << " at " << key;
+ }
+ return testing::AssertionSuccess();
+ }
+};
+
+TEST_F(StorageApiUnittest, RestoreCorruptedStorage) {
+ // Ensure a SettingsFrontend can be created on demand. The SettingsFrontend
+ // will be owned by the KeyedService system.
+ SettingsFrontend::GetFactoryInstance()->SetTestingFactory(
+ profile(), &CreateSettingsFrontendForTesting);
+
+ const char kKey[] = "key";
+ const char kValue[] = "value";
+ std::string result;
+
+ // Do a simple set/get combo to make sure the API works.
+ RunSetFunction(kKey, kValue);
+ EXPECT_TRUE(RunGetFunction(kKey, &result));
+ EXPECT_EQ(kValue, result);
+
+ // Corrupt the store. This is not as pretty as ideal, because we use knowledge
+ // of the underlying structure, but there's no real good way to corrupt a
+ // store other than directly modifying the files.
+ ValueStore* store =
+ settings_test_util::GetStorage(extension_ref(),
+ settings_namespace::LOCAL,
+ SettingsFrontend::Get(profile()));
+ ASSERT_TRUE(store);
+ SettingsStorageQuotaEnforcer* quota_store =
+ static_cast<SettingsStorageQuotaEnforcer*>(store);
+ LeveldbValueStore* leveldb_store =
+ static_cast<LeveldbValueStore*>(quota_store->get_delegate_for_test());
+ leveldb::WriteBatch batch;
+ batch.Put(kKey, "[{(.*+\"\'\\");
+ EXPECT_TRUE(leveldb_store->WriteToDbForTest(&batch));
+ EXPECT_TRUE(leveldb_store->Get(kKey)->IsCorrupted());
+
+ // Running another set should end up working (even though it will restore the
+ // store behind the scenes).
+ RunSetFunction(kKey, kValue);
+ EXPECT_TRUE(RunGetFunction(kKey, &result));
+ EXPECT_EQ(kValue, result);
+}
+
+} // namespace extensions
diff --git a/extensions/common/api/api.gyp b/extensions/common/api/api.gyp
index e6406c7..8297269 100644
--- a/extensions/common/api/api.gyp
+++ b/extensions/common/api/api.gyp
@@ -10,6 +10,8 @@
'sources': [
'<@(schema_files)',
],
+ # TODO(jschuh): http://crbug.com/167187 size_t -> int
+ 'msvs_disabled_warnings': [ 4267 ],
'includes': [
'../../../build/json_schema_bundle_compile.gypi',
'../../../build/json_schema_compile.gypi',
@@ -18,11 +20,13 @@
'chromium_code': 1,
'non_compiled_schema_files': [
],
+ # TODO: Eliminate these on Android. See crbug.com/305852.
'schema_files': [
'socket.idl',
'sockets_tcp.idl',
'sockets_tcp_server.idl',
'sockets_udp.idl',
+ 'storage.json',
],
'cc_dir': 'extensions/common/api',
'root_namespace': 'extensions::core_api',
diff --git a/extensions/common/api/storage.json b/extensions/common/api/storage.json
new file mode 100644
index 0000000..66678f8
--- /dev/null
+++ b/extensions/common/api/storage.json
@@ -0,0 +1,219 @@
+// Copyright 2014 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.
+
+[
+ {
+ "namespace": "storage",
+ "description": "Use the <code>chrome.storage</code> API to store, retrieve, and track changes to user data.",
+ "unprivileged": true,
+ "types": [
+ {
+ "id": "StorageChange",
+ "type": "object",
+ "properties": {
+ "oldValue": {
+ "type": "any",
+ "description": "The old value of the item, if there was an old value.",
+ "optional": true
+ },
+ "newValue": {
+ "type": "any",
+ "description": "The new value of the item, if there is a new value.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "StorageArea",
+ "type": "object",
+ "js_module": "StorageArea",
+ "functions": [
+ {
+ "name": "get",
+ "type": "function",
+ "description": "Gets one or more items from storage.",
+ "parameters": [
+ {
+ "name": "keys",
+ "choices": [
+ { "type": "string" },
+ { "type": "array", "items": { "type": "string" } },
+ {
+ "type": "object",
+ "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.",
+ "additionalProperties": { "type": "any" }
+ }
+ ],
+ "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object). An empty list or object will return an empty result object. Pass in <code>null</code> to get the entire contents of storage.",
+ "optional": true
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback with storage items, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [
+ {
+ "name": "items",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "description": "Object with items in their key-value mappings."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getBytesInUse",
+ "type": "function",
+ "description": "Gets the amount of space (in bytes) being used by one or more items.",
+ "parameters": [
+ {
+ "name": "keys",
+ "choices": [
+ { "type": "string" },
+ { "type": "array", "items": { "type": "string" } }
+ ],
+ "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in <code>null</code> to get the total usage of all of storage.",
+ "optional": true
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback with the amount of space being used by storage, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [
+ {
+ "name": "bytesInUse",
+ "type": "integer",
+ "description": "Amount of space being used in storage, in bytes."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "set",
+ "type": "function",
+ "description": "Sets multiple items.",
+ "parameters": [
+ {
+ "name": "items",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "description": "<p>An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.</p><p>Primitive values such as numbers will serialize as expected. Values with a <code>typeof</code> <code>\"object\"</code> and <code>\"function\"</code> will typically serialize to <code>{}</code>, with the exception of <code>Array</code> (serializes as expected), <code>Date</code>, and <code>Regex</code> (serialize using their <code>String</code> representation).</p>"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "description": "Removes one or more items from storage.",
+ "parameters": [
+ {
+ "name": "keys",
+ "choices": [
+ {"type": "string"},
+ {"type": "array", "items": {"type": "string"}}
+ ],
+ "description": "A single key or a list of keys for items to remove."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ },
+ {
+ "name": "clear",
+ "type": "function",
+ "description": "Removes all items from storage.",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onChanged",
+ "type": "function",
+ "description": "Fired when one or more items change.",
+ "parameters": [
+ {
+ "name": "changes",
+ "type": "object",
+ "additionalProperties": { "$ref": "StorageChange" },
+ "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item."
+ },
+ {
+ "name": "areaName",
+ "type": "string",
+ "description": "The name of the storage area (<code>\"sync\"</code>, <code>\"local\"</code> or <code>\"managed\"</code>) the changes are for."
+ }
+ ]
+ }
+ ],
+ "properties": {
+ "sync": {
+ "$ref": "StorageArea",
+ "description": "Items in the <code>sync</code> storage area are synced using Chrome Sync.",
+ "value": [ "sync" ],
+ "properties": {
+ "QUOTA_BYTES": {
+ "value": 102400,
+ "description": "The maximum total amount (in bytes) of data that can be stored in sync storage, as measured by the JSON stringification of every value plus every key's length. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+ },
+ "QUOTA_BYTES_PER_ITEM": {
+ "value": 4096,
+ "description": "The maximum size (in bytes) of each individual item in sync storage, as measured by the JSON stringification of its value plus its key length. Updates containing items larger than this limit will fail immediately and set $(ref:runtime.lastError)."
+ },
+ "MAX_ITEMS": {
+ "value": 512,
+ "description": "The maximum number of items that can be stored in sync storage. Updates that would cause this limit to be exceeded will fail immediately and set $(ref:runtime.lastError)."
+ },
+ "MAX_WRITE_OPERATIONS_PER_HOUR": {
+ "value": 1000,
+ "description": "The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each hour. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+ },
+ "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": {
+ "value": 10,
+ "description": "The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each minute, sustained over 10 minutes. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+ }
+ }
+ },
+ "local": {
+ "$ref": "StorageArea",
+ "description": "Items in the <code>local</code> storage area are local to each machine.",
+ "value": [ "local" ],
+ "properties": {
+ "QUOTA_BYTES": {
+ "value": 5242880,
+ "description": "The maximum amount (in bytes) of data that can be stored in local storage, as measured by the JSON stringification of every value plus every key's length. This value will be ignored if the extension has the <code>unlimitedStorage</code> permission. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+ }
+ }
+ },
+ "managed": {
+ "$ref": "StorageArea",
+ "description": "Items in the <code>managed</code> storage area are set by the domain administrator, and are read-only for the extension; trying to modify this namespace results in an error.",
+ "value": [ "managed" ]
+ }
+ }
+ }
+]
diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp
index 9e4a741..d3a8704 100644
--- a/extensions/extensions.gyp
+++ b/extensions/extensions.gyp
@@ -212,6 +212,8 @@
'browser/api/storage/settings_storage_factory.h',
'browser/api/storage/settings_storage_quota_enforcer.cc',
'browser/api/storage/settings_storage_quota_enforcer.h',
+ 'browser/api/storage/storage_api.cc',
+ 'browser/api/storage/storage_api.h',
'browser/api/storage/value_store_cache.cc',
'browser/api/storage/value_store_cache.h',
'browser/api/storage/weak_unlimited_settings_storage.cc',