summaryrefslogtreecommitdiffstats
path: root/webkit/dom_storage
diff options
context:
space:
mode:
authormarja@chromium.org <marja@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-27 12:22:55 +0000
committermarja@chromium.org <marja@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-27 12:22:55 +0000
commit99e258895f99dcbaeeb979b9da84983e7598b8b5 (patch)
tree5a9f31d14e2814a3070341e36e772096a62e259b /webkit/dom_storage
parent091f26196c6a0ca3cb6cebefec154048b01e600e (diff)
downloadchromium_src-99e258895f99dcbaeeb979b9da84983e7598b8b5.zip
chromium_src-99e258895f99dcbaeeb979b9da84983e7598b8b5.tar.gz
chromium_src-99e258895f99dcbaeeb979b9da84983e7598b8b5.tar.bz2
Add dom_storage::SessionStorageDatabase.
BUG=104292 TEST=SessionStorageDatabaseTest.* TBR=tony@chromium.org Review URL: http://codereview.chromium.org/10176005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@134259 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/dom_storage')
-rw-r--r--webkit/dom_storage/session_storage_database.cc658
-rw-r--r--webkit/dom_storage/session_storage_database.h202
-rw-r--r--webkit/dom_storage/session_storage_database_unittest.cc858
-rw-r--r--webkit/dom_storage/webkit_dom_storage.gypi3
4 files changed, 1721 insertions, 0 deletions
diff --git a/webkit/dom_storage/session_storage_database.cc b/webkit/dom_storage/session_storage_database.cc
new file mode 100644
index 0000000..e66d477
--- /dev/null
+++ b/webkit/dom_storage/session_storage_database.cc
@@ -0,0 +1,658 @@
+// 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 "webkit/dom_storage/session_storage_database.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+#include "third_party/leveldatabase/src/include/leveldb/options.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+// Layout of the database:
+// | key | value |
+// -----------------------------------------------------------------------
+// | map-1 | 2 (refcount, start of map-1-* keys)|
+// | map-1-a | b (a = b in map 1) |
+// | ... | |
+// | namespace- | dummy (start of namespace-* keys) |
+// | namespace-1 (1 = namespace id) | dummy (start of namespace-1-* keys)|
+// | namespace-1-origin1 | 1 (mapid) |
+// | namespace-1-origin2 | 2 |
+// | namespace-2 | dummy |
+// | namespace-2-origin1 | 1 (shallow copy) |
+// | namespace-2-origin2 | 2 (shallow copy) |
+// | namespace-3 | dummy |
+// | namespace-3-origin1 | 3 (deep copy) |
+// | namespace-3-origin2 | 2 (shallow copy) |
+// | next-namespace-id | 4 |
+// | next-map-id | 4 |
+
+namespace dom_storage {
+
+SessionStorageDatabase::SessionStorageDatabase(const FilePath& file_path)
+ : file_path_(file_path),
+ db_error_(false),
+ is_inconsistent_(false),
+ namespace_offset_(0) { }
+
+SessionStorageDatabase::~SessionStorageDatabase() {
+}
+
+void SessionStorageDatabase::ReadAreaValues(int64 namespace_id,
+ const GURL& origin,
+ ValuesMap* result) {
+ // We don't create a database if it doesn't exist. In that case, there is
+ // nothing to be added to the result.
+ if (!LazyOpen(false))
+ return;
+ std::string map_id;
+ bool exists;
+ if (!GetMapForArea(namespace_id, origin, &exists, &map_id))
+ return;
+ if (exists)
+ ReadMap(map_id, result, false);
+}
+
+bool SessionStorageDatabase::CommitAreaChanges(int64 namespace_id,
+ const GURL& origin,
+ bool clear_all_first,
+ const ValuesMap& changes) {
+ // Even if |changes| is empty, we need to write the appropriate placeholders
+ // in the database, so that it can be later shallow-copied succssfully.
+ if (!LazyOpen(true))
+ return false;
+
+ leveldb::WriteBatch batch;
+ // Ensure that the keys "namespace-" "namespace-N" (see the schema above)
+ // exist.
+ const bool kOkIfExists = true;
+ if (!CreateNamespace(namespace_id, kOkIfExists, &batch))
+ return false;
+
+ std::string map_id;
+ bool exists;
+ if (!GetMapForArea(namespace_id, origin, &exists, &map_id))
+ return false;
+ if (exists) {
+ // We shouldn't write data into a shallow copy. If this is a shallow copy,
+ // it's a caller error (not an inconsistency in the database).
+ int64 ref_count;
+ if (!GetMapRefCount(map_id, &ref_count))
+ return false;
+ if (!CallerErrorCheck(ref_count == 1))
+ return false;
+
+ if (clear_all_first) {
+ if (!ClearMap(map_id, &batch))
+ return false;
+ }
+ } else {
+ // Map doesn't exist, create it now if needed.
+ if (!changes.empty()) {
+ if (!CreateMapForArea(namespace_id, origin, &map_id, &batch))
+ return false;
+ }
+ }
+
+ WriteValuesToMap(map_id, changes, &batch);
+
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::CloneNamespace(int64 namespace_id,
+ int64 new_namespace_id) {
+ // Go through all origins in the namespace |namespace_id|, create placeholders
+ // for them in |new_namespace_id|, and associate them with the existing maps.
+
+ // Example, data before shallow copy:
+ // | map-1 | 1 (refcount) |
+ // | map-1-a | b |
+ // | namespace-1 (1 = namespace id) | dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+
+ // Example, data after shallow copy:
+ // | map-1 | 2 (inc. refcount) |
+ // | map-1-a | b |
+ // | namespace-1 (1 = namespace id) | dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+ // | namespace-2 | dummy |
+ // | namespace-2-origin1 | 1 (mapid) << references the same map
+
+ if (!LazyOpen(true))
+ return false;
+
+ leveldb::WriteBatch batch;
+ const bool kOkIfExists = false;
+ if (!CreateNamespace(new_namespace_id, kOkIfExists, &batch))
+ return false;
+
+ std::map<std::string, std::string> areas;
+ if (!GetAreasInNamespace(namespace_id, &areas))
+ return false;
+
+ for (std::map<std::string, std::string>::const_iterator it = areas.begin();
+ it != areas.end(); ++it) {
+ const std::string& origin = it->first;
+ const std::string& map_id = it->second;
+ if (!IncreaseMapRefCount(map_id, &batch))
+ return false;
+ AddAreaToNamespace(new_namespace_id, origin, map_id, &batch);
+ }
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::DeepCopyArea(int64 namespace_id,
+ const GURL& origin) {
+ // Example, data before deep copy:
+ // | namespace-1 (1 = namespace id) | dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+ // | namespace-2 | dummy |
+ // | namespace-2-origin1 | 1 (mapid) << references the same map
+ // | map-1 | 2 (refcount) |
+ // | map-1-a | b |
+
+ // Example, data after deep copy copy:
+ // | namespace-1 (1 = namespace id) | dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+ // | namespace-2 | dummy |
+ // | namespace-2-origin1 | 2 (mapid) << references the new map
+ // | map-1 | 1 (dec. refcount) |
+ // | map-1-a | b |
+ // | map-2 | 1 (refcount) |
+ // | map-2-a | b |
+
+ if (!LazyOpen(true))
+ return false;
+
+ std::string old_map_id;
+ bool exists;
+ if (!GetMapForArea(namespace_id, origin, &exists, &old_map_id))
+ return false;
+
+ // If the area doesn't exist, or if it's not a shallow copy, it's a caller
+ // error.
+ if (!CallerErrorCheck(exists))
+ return false;
+ int64 ref_count;
+ if (!GetMapRefCount(old_map_id, &ref_count))
+ return false;
+ if (!CallerErrorCheck(ref_count > 1))
+ return false;
+
+ leveldb::WriteBatch batch;
+ std::string new_map_id;
+ if (!CreateMapForArea(namespace_id, origin, &new_map_id, &batch))
+ return false;
+
+ // Copy the values in the map.
+ ValuesMap values;
+ if (!ReadMap(old_map_id, &values, false))
+ return false;
+ WriteValuesToMap(new_map_id, values, &batch);
+
+ if (!DecreaseMapRefCount(old_map_id, 1, &batch))
+ return false;
+
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::DeleteArea(int64 namespace_id,
+ const GURL& origin) {
+ if (!LazyOpen(false)) {
+ // No need to create the database if it doesn't exist.
+ return true;
+ }
+ leveldb::WriteBatch batch;
+ if (!DeleteArea(namespace_id, origin.spec(), &batch))
+ return false;
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::DeleteNamespace(int64 namespace_id) {
+ if (!LazyOpen(false)) {
+ // No need to create the database if it doesn't exist.
+ return true;
+ }
+ // Itereate through the areas in the namespace.
+ leveldb::WriteBatch batch;
+ std::map<std::string, std::string> areas;
+ if (!GetAreasInNamespace(namespace_id, &areas))
+ return false;
+ for (std::map<std::string, std::string>::const_iterator it = areas.begin();
+ it != areas.end(); ++it) {
+ const std::string& origin = it->first;
+ if (!DeleteArea(namespace_id, origin, &batch))
+ return false;
+ }
+ batch.Delete(NamespaceStartKey(namespace_id, namespace_offset_));
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
+ base::AutoLock auto_lock(db_lock_);
+ if (db_error_ || is_inconsistent_) {
+ // Don't try to open a database that we know has failed already.
+ return false;
+ }
+ if (IsOpen())
+ return true;
+
+ if (!create_if_needed &&
+ (!file_util::PathExists(file_path_) ||
+ file_util::IsDirectoryEmpty(file_path_))) {
+ // If the directory doesn't exist already and we haven't been asked to
+ // create a file on disk, then we don't bother opening the database. This
+ // means we wait until we absolutely need to put something onto disk before
+ // we do so.
+ return false;
+ }
+
+ leveldb::DB* db;
+ leveldb::Status s = TryToOpen(file_path_, &db);
+ if (!s.ok()) {
+ LOG(WARNING) << "Failed to open leveldb in " << file_path_.value()
+ << ", error: " << s.ToString();
+ DCHECK(db == NULL);
+
+ // Clear the directory and try again.
+ file_util::Delete(file_path_, true);
+ s = TryToOpen(file_path_, &db);
+ if (!s.ok()) {
+ LOG(WARNING) << "Failed to open leveldb in " << file_path_.value()
+ << ", error: " << s.ToString();
+ DCHECK(db == NULL);
+ db_error_ = true;
+ return false;
+ }
+ }
+ db_.reset(db);
+
+ return GetNextNamespaceId(&namespace_offset_);
+}
+
+leveldb::Status SessionStorageDatabase::TryToOpen(const FilePath& file_path,
+ leveldb::DB** db) {
+ leveldb::Options options;
+ // The directory exists but a valid leveldb database might not exist inside it
+ // (e.g., a subset of the needed files might be missing). Handle this
+ // situation gracefully by creating the database now.
+ options.create_if_missing = true;
+#if defined(OS_WIN)
+ return leveldb::DB::Open(options, WideToUTF8(file_path.value()), db);
+#elif defined(OS_POSIX)
+ return leveldb::DB::Open(options, file_path.value(), db);
+#endif
+}
+
+bool SessionStorageDatabase::IsOpen() const {
+ return db_.get() != NULL;
+}
+
+bool SessionStorageDatabase::CallerErrorCheck(bool ok) const {
+ DCHECK(ok);
+ return ok;
+}
+
+bool SessionStorageDatabase::ConsistencyCheck(bool ok) {
+ if (ok)
+ return true;
+ base::AutoLock auto_lock(db_lock_);
+ DCHECK(false);
+ is_inconsistent_ = true;
+ // We cannot recover the database during this run, e.g., the upper layer can
+ // have a different understanding of the database state (shallow and deep
+ // copies).
+ // TODO(marja): Error handling.
+ return false;
+}
+
+bool SessionStorageDatabase::DatabaseErrorCheck(bool ok) {
+ if (ok)
+ return true;
+ base::AutoLock auto_lock(db_lock_);
+ db_error_ = true;
+ // TODO(marja): Error handling.
+ return false;
+}
+
+bool SessionStorageDatabase::CreateNamespace(int64 namespace_id,
+ bool ok_if_exists,
+ leveldb::WriteBatch* batch) {
+ std::string namespace_prefix = NamespacePrefix();
+ std::string dummy;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), namespace_prefix,
+ &dummy);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ if (s.IsNotFound())
+ batch->Put(namespace_prefix, "");
+
+ std::string namespace_start_key =
+ NamespaceStartKey(namespace_id, namespace_offset_);
+ s = db_->Get(leveldb::ReadOptions(), namespace_start_key, &dummy);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ if (s.IsNotFound()) {
+ batch->Put(namespace_start_key, "");
+ return UpdateNextNamespaceId(namespace_id, batch);
+ }
+ return CallerErrorCheck(ok_if_exists);
+}
+
+bool SessionStorageDatabase::GetNextNamespaceId(int64* next_namespace_id) {
+ std::string next_namespace_id_string;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), NextNamespaceIdKey(),
+ &next_namespace_id_string);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ if (s.IsNotFound()) {
+ *next_namespace_id = 0;
+ return true;
+ }
+ bool conversion_ok =
+ base::StringToInt64(next_namespace_id_string, next_namespace_id);
+ return ConsistencyCheck(conversion_ok);
+}
+
+bool SessionStorageDatabase::UpdateNextNamespaceId(int64 namespace_id,
+ leveldb::WriteBatch* batch) {
+ int64 next_namespace_id;
+ if (!GetNextNamespaceId(&next_namespace_id))
+ return false;
+ if (next_namespace_id < namespace_id + namespace_offset_ + 1) {
+ next_namespace_id = namespace_id + namespace_offset_ + 1;
+ batch->Put(NextNamespaceIdKey(), base::Int64ToString(next_namespace_id));
+ }
+ return true;
+}
+
+bool SessionStorageDatabase::GetAreasInNamespace(
+ int64 namespace_id,
+ std::map<std::string, std::string>* areas) {
+ return GetAreasInNamespace(NamespaceIdStr(namespace_id, namespace_offset_),
+ areas);
+}
+
+bool SessionStorageDatabase::GetAreasInNamespace(
+ const std::string& namespace_id_str,
+ std::map<std::string, std::string>* areas) {
+ std::string namespace_start_key = NamespaceStartKey(namespace_id_str);
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+ it->Seek(namespace_start_key);
+ if (it->status().IsNotFound()) {
+ // The namespace_start_key is not found when the namespace doesn't contain
+ // any areas. We don't need to do anything.
+ return true;
+ }
+ if (!DatabaseErrorCheck(it->status().ok()))
+ return false;
+
+ // Skip the dummy entry "namespace-<namespaceid>" and iterate the origins.
+ for (it->Next(); it->Valid(); it->Next()) {
+ std::string key = it->key().ToString();
+ if (key.find(namespace_start_key) != 0) {
+ // Iterated past the origins for this namespace.
+ break;
+ }
+ size_t second_dash = key.find('-', namespace_start_key.length());
+ if (!ConsistencyCheck(second_dash != std::string::npos))
+ return false;
+ std::string origin = key.substr(second_dash + 1);
+ std::string map_id = it->value().ToString();
+ (*areas)[origin] = map_id;
+ }
+ return true;
+}
+
+void SessionStorageDatabase::AddAreaToNamespace(int64 namespace_id,
+ const std::string& origin,
+ const std::string& map_id,
+ leveldb::WriteBatch* batch) {
+ std::string namespace_key = NamespaceKey(
+ NamespaceIdStr(namespace_id, namespace_offset_), origin);
+ batch->Put(namespace_key, map_id);
+}
+
+bool SessionStorageDatabase::DeleteArea(int64 namespace_id,
+ const std::string& origin,
+ leveldb::WriteBatch* batch) {
+ return DeleteArea(NamespaceIdStr(namespace_id, namespace_offset_),
+ origin, batch);
+}
+
+bool SessionStorageDatabase::DeleteArea(const std::string& namespace_id_str,
+ const std::string& origin,
+ leveldb::WriteBatch* batch) {
+ std::string map_id;
+ bool exists;
+ if (!GetMapForArea(namespace_id_str, origin, &exists, &map_id))
+ return false;
+ if (!exists)
+ return true; // Nothing to delete.
+ if (!DecreaseMapRefCount(map_id, 1, batch))
+ return false;
+ std::string namespace_key = NamespaceKey(namespace_id_str, origin);
+ batch->Delete(namespace_key);
+ return true;
+}
+
+bool SessionStorageDatabase::GetMapForArea(int64 namespace_id,
+ const GURL& origin,
+ bool* exists,
+ std::string* map_id) {
+ return GetMapForArea(
+ base::Int64ToString(namespace_id + namespace_offset_),
+ origin.spec(), exists, map_id);
+}
+
+bool SessionStorageDatabase::GetMapForArea(const std::string& namespace_id_str,
+ const std::string& origin,
+ bool* exists, std::string* map_id) {
+ std::string namespace_key = NamespaceKey(namespace_id_str, origin);
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), namespace_key, map_id);
+ if (s.IsNotFound()) {
+ *exists = false;
+ return true;
+ }
+ *exists = true;
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::CreateMapForArea(int64 namespace_id,
+ const GURL& origin,
+ std::string* map_id,
+ leveldb::WriteBatch* batch) {
+ std::string next_map_id_key = NextMapIdKey();
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), next_map_id_key, map_id);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ int64 next_map_id = 0;
+ if (s.IsNotFound()) {
+ *map_id = "0";
+ } else {
+ bool conversion_ok = base::StringToInt64(*map_id, &next_map_id);
+ if (!ConsistencyCheck(conversion_ok))
+ return false;
+ }
+ batch->Put(next_map_id_key, base::Int64ToString(++next_map_id));
+ std::string namespace_key =
+ NamespaceKey(namespace_id, namespace_offset_, origin);
+ batch->Put(namespace_key, *map_id);
+ batch->Put(MapRefCountKey(*map_id), "1");
+ return true;
+}
+
+bool SessionStorageDatabase::ReadMap(const std::string& map_id,
+ ValuesMap* result,
+ bool only_keys) {
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+ std::string map_start_key = MapRefCountKey(map_id);
+ it->Seek(map_start_key);
+ // The map needs to exist, otherwise we have a stale map_id in the database.
+ if (!ConsistencyCheck(!it->status().IsNotFound()))
+ return false;
+ if (!DatabaseErrorCheck(it->status().ok()))
+ return false;
+ const int kPrefixLength = std::string(MapPrefix()).length();
+ // Skip the dummy entry "map-<mapid>".
+ for (it->Next(); it->Valid(); it->Next()) {
+ // Key is of the form "map-<mapid>-<key>".
+ std::string key = it->key().ToString();
+ size_t second_dash = key.find('-', kPrefixLength);
+ if (second_dash == std::string::npos ||
+ key.substr(kPrefixLength, second_dash - kPrefixLength) != map_id) {
+ // Iterated beyond the keys in this map.
+ break;
+ }
+ string16 key16 = UTF8ToUTF16(key.substr(second_dash + 1));
+ if (only_keys) {
+ (*result)[key16] = NullableString16(true);
+ } else {
+ // Convert the raw data stored in std::string (it->value()) to raw data
+ // stored in string16.
+ size_t len = it->value().size() / sizeof(char16);
+ const char16* data_ptr =
+ reinterpret_cast<const char16*>(it->value().data());
+ (*result)[key16] = NullableString16(string16(data_ptr, len), false);
+ }
+ }
+ return true;
+}
+
+void SessionStorageDatabase::WriteValuesToMap(const std::string& map_id,
+ const ValuesMap& values,
+ leveldb::WriteBatch* batch) {
+ for (ValuesMap::const_iterator it = values.begin(); it != values.end();
+ ++it) {
+ NullableString16 value = it->second;
+ std::string key = MapKey(map_id, UTF16ToUTF8(it->first));
+ if (value.is_null()) {
+ batch->Delete(key);
+ } else {
+ // Convert the raw data stored in string16 to raw data stored in
+ // std::string.
+ const char* data = reinterpret_cast<const char*>(value.string().data());
+ size_t size = value.string().size() * 2;
+ batch->Put(key, leveldb::Slice(data, size));
+ }
+ }
+}
+
+bool SessionStorageDatabase::GetMapRefCount(const std::string& map_id,
+ int64* ref_count) {
+ std::string ref_count_string;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(),
+ MapRefCountKey(map_id), &ref_count_string);
+ if (!ConsistencyCheck(s.ok()))
+ return false;
+ bool conversion_ok = base::StringToInt64(ref_count_string, ref_count);
+ return ConsistencyCheck(conversion_ok);
+}
+
+bool SessionStorageDatabase::IncreaseMapRefCount(const std::string& map_id,
+ leveldb::WriteBatch* batch) {
+ // Increase the ref count for the map.
+ int64 old_ref_count;
+ if (!GetMapRefCount(map_id, &old_ref_count))
+ return false;
+ batch->Put(MapRefCountKey(map_id), base::Int64ToString(++old_ref_count));
+ return true;
+}
+
+bool SessionStorageDatabase::DecreaseMapRefCount(const std::string& map_id,
+ int decrease,
+ leveldb::WriteBatch* batch) {
+ // Decrease the ref count for the map.
+ int64 ref_count;
+ if (!GetMapRefCount(map_id, &ref_count))
+ return false;
+ if (!ConsistencyCheck(decrease <= ref_count))
+ return false;
+ ref_count -= decrease;
+ if (ref_count > 0) {
+ batch->Put(MapRefCountKey(map_id), base::Int64ToString(ref_count));
+ } else {
+ // Clear all keys in the map.
+ if (!ClearMap(map_id, batch))
+ return false;
+ batch->Delete(MapRefCountKey(map_id));
+ }
+ return true;
+}
+
+bool SessionStorageDatabase::ClearMap(const std::string& map_id,
+ leveldb::WriteBatch* batch) {
+ ValuesMap values;
+ if (!ReadMap(map_id, &values, true))
+ return false;
+ for (ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it)
+ batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first)));
+ return true;
+}
+
+std::string SessionStorageDatabase::NamespaceStartKey(
+ const std::string& namespace_id_str) {
+ return base::StringPrintf("namespace-%s", namespace_id_str.c_str());
+}
+
+std::string SessionStorageDatabase::NamespaceStartKey(int64 namespace_id,
+ int64 namespace_offset) {
+ return NamespaceStartKey(NamespaceIdStr(namespace_id, namespace_offset));
+}
+
+std::string SessionStorageDatabase::NamespaceKey(
+ const std::string& namespace_id_str, const std::string& origin) {
+ return base::StringPrintf("namespace-%s-%s", namespace_id_str.c_str(),
+ origin.c_str());
+}
+
+std::string SessionStorageDatabase::NamespaceKey(
+ int64 namespace_id, int64 namespace_offset, const GURL& origin) {
+ return NamespaceKey(NamespaceIdStr(namespace_id, namespace_offset),
+ origin.spec());
+}
+
+std::string SessionStorageDatabase::NamespaceIdStr(int64 namespace_id,
+ int64 namespace_offset) {
+ return base::Int64ToString(namespace_id + namespace_offset);
+}
+
+const char* SessionStorageDatabase::NamespacePrefix() {
+ return "namespace-";
+}
+
+std::string SessionStorageDatabase::MapRefCountKey(const std::string& map_id) {
+ return base::StringPrintf("map-%s", map_id.c_str());
+}
+
+std::string SessionStorageDatabase::MapKey(const std::string& map_id,
+ const std::string& key) {
+ return base::StringPrintf("map-%s-%s", map_id.c_str(), key.c_str());
+}
+
+const char* SessionStorageDatabase::MapPrefix() {
+ return "map-";
+}
+
+const char* SessionStorageDatabase::NextNamespaceIdKey() {
+ return "next-namespace-id";
+}
+
+const char* SessionStorageDatabase::NextMapIdKey() {
+ return "next-map-id";
+}
+
+} // namespace dom_storage
diff --git a/webkit/dom_storage/session_storage_database.h b/webkit/dom_storage/session_storage_database.h
new file mode 100644
index 0000000..5230f37
--- /dev/null
+++ b/webkit/dom_storage/session_storage_database.h
@@ -0,0 +1,202 @@
+// 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 WEBKIT_DOM_STORAGE_SESSION_STORAGE_DATABASE_H_
+#define WEBKIT_DOM_STORAGE_SESSION_STORAGE_DATABASE_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+#include "webkit/dom_storage/dom_storage_types.h"
+
+class GURL;
+
+namespace leveldb {
+class DB;
+class WriteBatch;
+} // namespace leveldb
+
+namespace dom_storage {
+
+// SessionStorageDatabase holds the data from multiple namespaces and multiple
+// origins. All DomStorageAreas for session storage share the same
+// SessionStorageDatabase.
+class SessionStorageDatabase :
+ public base::RefCountedThreadSafe<SessionStorageDatabase> {
+ public:
+ explicit SessionStorageDatabase(const FilePath& file_path);
+
+ // Reads the (key, value) pairs for |namespace_id| and |origin|. |result| is
+ // assumed to be empty and any duplicate keys will be overwritten. If the
+ // database exists on disk then it will be opened. If it does not exist then
+ // it will not be created and |result| will be unmodified.
+ void ReadAreaValues(int64 namespace_id,
+ const GURL& origin,
+ ValuesMap* result);
+
+ // Updates the data for |namespace_id| and |origin|. Will remove all keys
+ // before updating the database if |clear_all_first| is set. Then all entries
+ // in |changes| will be examined - keys mapped to a null NullableString16 will
+ // be removed and all others will be inserted/updated as appropriate.
+ bool CommitAreaChanges(int64 namespace_id,
+ const GURL& origin,
+ bool clear_all_first,
+ const ValuesMap& changes);
+
+ // Creates shallow copies of the areas for |namespace_id| and associates them
+ // with |new_namespace_id|.
+ bool CloneNamespace(int64 namespace_id, int64 new_namespace_id);
+
+ // Creates a deep copy of the area for |namespace_id| and |origin|.
+ bool DeepCopyArea(int64 namespace_id, const GURL& origin);
+
+ // Deletes the data for |namespace_id| and |origin|.
+ bool DeleteArea(int64 namespace_id, const GURL& origin);
+
+ // Deletes the data for |namespace_id|.
+ bool DeleteNamespace(int64 namespace_id);
+
+ private:
+ friend class base::RefCountedThreadSafe<SessionStorageDatabase>;
+ friend class SessionStorageDatabaseTest;
+
+ ~SessionStorageDatabase();
+
+ bool LazyOpen(bool create_if_needed);
+ leveldb::Status TryToOpen(const FilePath& file_path, leveldb::DB** db);
+ bool IsOpen() const;
+
+ // Helpers for checking caller erros, invariants and database errors. All
+ // these return |ok|, for chaining.
+ bool CallerErrorCheck(bool ok) const;
+ bool ConsistencyCheck(bool ok);
+ bool DatabaseErrorCheck(bool ok);
+
+ // Helper functions. All return true if the operation succeeded, and false if
+ // it failed (a database error or a consistency error). If the return type is
+ // void, the operation cannot fail. If they return false, ConsistencyCheck or
+ // DatabaseErrorCheck have already been called.
+
+ // Creates a namespace for |namespace_id| and updates the next namespace id if
+ // needed. If |ok_if_exists| is false, checks that the namespace didn't exist
+ // before.
+ bool CreateNamespace(int64 namespace_id,
+ bool ok_if_exists,
+ leveldb::WriteBatch* batch);
+ // Reads the next namespace id.
+ bool GetNextNamespaceId(int64* next_namespace_id);
+ bool UpdateNextNamespaceId(int64 namespace_id,
+ leveldb::WriteBatch* batch);
+ // Reads the areas assoiated with |namespace_id| and puts the (origin, map_id)
+ // pairs into |areas|.
+ bool GetAreasInNamespace(int64 namespace_id,
+ std::map<std::string, std::string>* areas);
+ bool GetAreasInNamespace(const std::string& namespace_id_str,
+ std::map<std::string, std::string>* areas);
+
+ // Adds an association between |origin| and |map_id| into the namespace
+ // |namespace_id|.
+ void AddAreaToNamespace(int64 namespace_id,
+ const std::string& origin,
+ const std::string& map_id,
+ leveldb::WriteBatch* batch);
+
+ // Helpers for deleting data for |namespace_id| and |origin|.
+ bool DeleteArea(int64 namespace_id,
+ const std::string& origin,
+ leveldb::WriteBatch* batch);
+ bool DeleteArea(const std::string& namespace_id_str,
+ const std::string& origin,
+ leveldb::WriteBatch* batch);
+
+ // Retrieves the map id for |namespace_id| and |origin|. It's not an error if
+ // the map doesn't exist.
+ bool GetMapForArea(int64 namespace_id,
+ const GURL& origin,
+ bool* exists,
+ std::string* map_id);
+ bool GetMapForArea(const std::string& namespace_id_str,
+ const std::string& origin,
+ bool* exists,
+ std::string* map_id);
+
+ // Creates a new map for |namespace_id| and |origin|. |map_id| will hold the
+ // id of the created map. If there is a map for |namespace_id| and |origin|,
+ // this just overwrites the map id. The caller is responsible for decreasing
+ // the ref count.
+ bool CreateMapForArea(int64 namespace_id,
+ const GURL& origin,
+ std::string* map_id,
+ leveldb::WriteBatch* batch);
+ // Reads the contents of the map |map_id| into |result|. If |only_keys| is
+ // true, only keys are aread from the database and the values in |result| will
+ // be empty.
+ bool ReadMap(const std::string& map_id,
+ ValuesMap* result,
+ bool only_keys);
+ // Writes |values| into the map |map_id|.
+ void WriteValuesToMap(const std::string& map_id,
+ const ValuesMap& values,
+ leveldb::WriteBatch* batch);
+
+ bool GetMapRefCount(const std::string& map_id, int64* ref_count);
+ bool IncreaseMapRefCount(const std::string& map_id,
+ leveldb::WriteBatch* batch);
+ // Decreases the ref count of a map by |decrease|. If the ref count goes to 0,
+ // deletes the map.
+ bool DecreaseMapRefCount(const std::string& map_id,
+ int decrease,
+ leveldb::WriteBatch* batch);
+
+ // Deletes all values in |map_id|.
+ bool ClearMap(const std::string& map_id, leveldb::WriteBatch* batch);
+
+ // Helper functions for creating the keys needed for the schema.
+ static std::string NamespaceStartKey(const std::string& namespace_id_str);
+ static std::string NamespaceStartKey(int64 namespace_id,
+ int64 namespace_offset);
+ static std::string NamespaceKey(const std::string& namespace_id_str,
+ const std::string& origin);
+ static std::string NamespaceKey(int64 namespace_id,
+ int64 namespace_offset,
+ const GURL& origin);
+ static std::string NamespaceIdStr(int64 namespace_id, int64 namespace_offset);
+ static const char* NamespacePrefix();
+ static std::string MapRefCountKey(const std::string& map_id);
+ static std::string MapKey(const std::string& map_id, const std::string& key);
+ static const char* MapPrefix();
+ static const char* NextNamespaceIdKey();
+ static const char* NextMapIdKey();
+
+ scoped_ptr<leveldb::DB> db_;
+ FilePath file_path_;
+
+ // For protecting the database opening code.
+ base::Lock db_lock_;
+
+ // True if a database error has occurred (e.g., cannot read data).
+ bool db_error_;
+ // True if the database is in an inconsistent state.
+ bool is_inconsistent_;
+
+ // On startup, we read the next ununsed namespace id from the database. It
+ // will be the offset for namespace ids. The actual id of a namespace in the
+ // database will be: id passed to the API function + namespace_offset_. The
+ // namespace ids which are handled as int64 (named namespace_id) don't contain
+ // the offset yet. The namespaces ids which are handled as strings (named
+ // namesapce_id_str) contain the offset.
+ int64 namespace_offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStorageDatabase);
+};
+
+} // namespace dom_storage
+
+#endif // WEBKIT_DOM_STORAGE_SESSION_STORAGE_DATABASE_H_
diff --git a/webkit/dom_storage/session_storage_database_unittest.cc b/webkit/dom_storage/session_storage_database_unittest.cc
new file mode 100644
index 0000000..c047009
--- /dev/null
+++ b/webkit/dom_storage/session_storage_database_unittest.cc
@@ -0,0 +1,858 @@
+// 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 "webkit/dom_storage/session_storage_database.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/options.h"
+#include "webkit/dom_storage/dom_storage_types.h"
+
+namespace dom_storage {
+
+class SessionStorageDatabaseTest : public testing::Test {
+ public:
+ SessionStorageDatabaseTest();
+ virtual ~SessionStorageDatabaseTest();
+ virtual void SetUp() OVERRIDE;
+
+ protected:
+ typedef std::map<std::string, std::string> DataMap;
+
+ // Helpers.
+ static bool IsNamespaceKey(const std::string& key,
+ int64* namespace_id);
+ static bool IsNamespaceOriginKey(const std::string& key,
+ int64* namespace_id);
+ static bool IsMapRefCountKey(const std::string& key,
+ int64* map_id);
+ static bool IsMapValueKey(const std::string& key,
+ int64* map_id);
+ void ResetDatabase();
+ void ReadData(DataMap* data) const;
+ void CheckDatabaseConsistency() const;
+ void CheckEmptyDatabase() const;
+ void DumpData() const;
+ void CheckAreaData(int64 namespace_id,
+ const GURL& origin,
+ const ValuesMap& reference) const;
+ void CompareValuesMaps(const ValuesMap& map1, const ValuesMap& map2) const;
+ std::string GetMapForArea(int64 namespace_id,
+ const GURL& origin) const;
+ int64 GetMapRefCount(const std::string& map_id) const;
+ int64 NextNamespaceId() const;
+
+ ScopedTempDir temp_dir_;
+ scoped_refptr<SessionStorageDatabase> db_;
+
+ // Test data.
+ const GURL kOrigin1;
+ const GURL kOrigin2;
+ const string16 kKey1;
+ const string16 kKey2;
+ const string16 kKey3;
+ const NullableString16 kValue1;
+ const NullableString16 kValue2;
+ const NullableString16 kValue3;
+ const NullableString16 kValue4;
+ const NullableString16 kValueNull;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStorageDatabaseTest);
+};
+
+SessionStorageDatabaseTest::SessionStorageDatabaseTest()
+ : kOrigin1("http://www.origin1.com"),
+ kOrigin2("http://www.origin2.com"),
+ kKey1(ASCIIToUTF16("key1")),
+ kKey2(ASCIIToUTF16("key2")),
+ kKey3(ASCIIToUTF16("key3")),
+ kValue1(NullableString16(ASCIIToUTF16("value1"), false)),
+ kValue2(NullableString16(ASCIIToUTF16("value2"), false)),
+ kValue3(NullableString16(ASCIIToUTF16("value3"), false)),
+ kValue4(NullableString16(ASCIIToUTF16("value4"), false)),
+ kValueNull(NullableString16(true)) { }
+
+SessionStorageDatabaseTest::~SessionStorageDatabaseTest() { }
+
+void SessionStorageDatabaseTest::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ResetDatabase();
+}
+
+void SessionStorageDatabaseTest::ResetDatabase() {
+ db_ = new SessionStorageDatabase(temp_dir_.path());
+ ASSERT_TRUE(db_->LazyOpen(true));
+}
+
+// static
+bool SessionStorageDatabaseTest::IsNamespaceKey(const std::string& key,
+ int64* namespace_id) {
+ std::string namespace_prefix = SessionStorageDatabase::NamespacePrefix();
+ if (key.find(namespace_prefix) != 0)
+ return false;
+ if (key == namespace_prefix)
+ return false;
+
+ size_t second_dash = key.find('-', namespace_prefix.length());
+ if (second_dash != std::string::npos)
+ return false;
+
+ // Key is of the form "namespace-<namespaceid>".
+ std::string namespace_id_str = key.substr(namespace_prefix.length());
+ bool conversion_ok = base::StringToInt64(namespace_id_str, namespace_id);
+ EXPECT_TRUE(conversion_ok);
+ return true;
+}
+
+// static
+bool SessionStorageDatabaseTest::IsNamespaceOriginKey(const std::string& key,
+ int64* namespace_id) {
+ std::string namespace_prefix = SessionStorageDatabase::NamespacePrefix();
+ if (key.find(namespace_prefix) != 0)
+ return false;
+ size_t second_dash = key.find('-', namespace_prefix.length());
+ if (second_dash == std::string::npos)
+ return false;
+ // Key is of the form "namespace-<namespaceid>-<origin>", and the value
+ // is the map id.
+ std::string namespace_id_str =
+ key.substr(namespace_prefix.length(),
+ second_dash - namespace_prefix.length());
+ bool conversion_ok = base::StringToInt64(namespace_id_str, namespace_id);
+ EXPECT_TRUE(conversion_ok);
+ return true;
+}
+
+// static
+bool SessionStorageDatabaseTest::IsMapRefCountKey(const std::string& key,
+ int64* map_id) {
+ std::string map_prefix = SessionStorageDatabase::MapPrefix();
+ if (key.find(map_prefix) != 0)
+ return false;
+ size_t second_dash = key.find('-', map_prefix.length());
+ if (second_dash != std::string::npos)
+ return false;
+ // Key is of the form "map-<mapid>" and the value is the ref count.
+ std::string map_id_str = key.substr(map_prefix.length(), second_dash);
+ bool conversion_ok = base::StringToInt64(map_id_str, map_id);
+ EXPECT_TRUE(conversion_ok);
+ return true;
+}
+
+// static
+bool SessionStorageDatabaseTest::IsMapValueKey(const std::string& key,
+ int64* map_id) {
+ std::string map_prefix = SessionStorageDatabase::MapPrefix();
+ if (key.find(map_prefix) != 0)
+ return false;
+ size_t second_dash = key.find('-', map_prefix.length());
+ if (second_dash == std::string::npos)
+ return false;
+ // Key is of the form "map-<mapid>-key".
+ std::string map_id_str =
+ key.substr(map_prefix.length(), second_dash - map_prefix.length());
+ bool conversion_ok = base::StringToInt64(map_id_str, map_id);
+ EXPECT_TRUE(conversion_ok);
+ return true;
+}
+
+void SessionStorageDatabaseTest::ReadData(DataMap* data) const {
+ leveldb::DB* leveldb = db_->db_.get();
+ scoped_ptr<leveldb::Iterator> it(
+ leveldb->NewIterator(leveldb::ReadOptions()));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ (*data)[it->key().ToString()] = it->value().ToString();
+ }
+}
+
+void SessionStorageDatabaseTest::CheckDatabaseConsistency() const {
+ DataMap data;
+ ReadData(&data);
+ // Empty db is ok.
+ if (data.empty())
+ return;
+
+ // For detecting rubbish keys.
+ size_t valid_keys = 0;
+
+ std::string next_namespace_id_key =
+ SessionStorageDatabase::NextNamespaceIdKey();
+ std::string next_map_id_key = SessionStorageDatabase::NextMapIdKey();
+ // Check the namespace start key.
+ if (data.find(SessionStorageDatabase::NamespacePrefix()) == data.end()) {
+ // If there is no namespace start key, the database may contain only counter
+ // keys.
+ for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
+ ASSERT_TRUE(it->first == next_namespace_id_key ||
+ it->first == next_map_id_key);
+ }
+ return;
+ }
+ ++valid_keys;
+
+ // Iterate the "namespace-" keys.
+ std::set<int64> found_namespace_ids;
+ int64 max_namespace_id = -1;
+ std::map<int64, int64> expected_map_refcounts;
+ int64 max_map_id = -1;
+
+ for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
+ int64 namespace_id;
+ std::string origin;
+ if (IsNamespaceKey(it->first, &namespace_id)) {
+ ASSERT_GT(namespace_id, 0);
+ found_namespace_ids.insert(namespace_id);
+ max_namespace_id = std::max(namespace_id, max_namespace_id);
+ ++valid_keys;
+ } else if (IsNamespaceOriginKey(
+ it->first, &namespace_id)) {
+ // Check that the corresponding "namespace-<namespaceid>" key exists. It
+ // has been read by now, since the keys are stored in order.
+ ASSERT_TRUE(found_namespace_ids.find(namespace_id) !=
+ found_namespace_ids.end());
+ int64 map_id;
+ bool conversion_ok = base::StringToInt64(it->second, &map_id);
+ ASSERT_TRUE(conversion_ok);
+ ASSERT_GE(map_id, 0);
+ ++expected_map_refcounts[map_id];
+ max_map_id = std::max(map_id, max_map_id);
+ ++valid_keys;
+ }
+ }
+ if (max_namespace_id != -1) {
+ // The database contains namespaces.
+ ASSERT_TRUE(data.find(next_namespace_id_key) != data.end());
+ int64 next_namespace_id;
+ bool conversion_ok =
+ base::StringToInt64(data[next_namespace_id_key], &next_namespace_id);
+ ASSERT_TRUE(conversion_ok);
+ ASSERT_GT(next_namespace_id, max_namespace_id);
+ }
+ if (max_map_id != -1) {
+ // The database contains maps.
+ ASSERT_TRUE(data.find(next_map_id_key) != data.end());
+ int64 next_map_id;
+ bool conversion_ok =
+ base::StringToInt64(data[next_map_id_key], &next_map_id);
+ ASSERT_TRUE(conversion_ok);
+ ASSERT_GT(next_map_id, max_map_id);
+ }
+
+ // Iterate the "map-" keys.
+ std::set<int64> found_map_ids;
+ for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
+ int64 map_id;
+ if (IsMapRefCountKey(it->first, &map_id)) {
+ int64 ref_count;
+ bool conversion_ok = base::StringToInt64(it->second, &ref_count);
+ ASSERT_TRUE(conversion_ok);
+ // Check that the map is not stale.
+ ASSERT_GT(ref_count, 0);
+ ASSERT_TRUE(expected_map_refcounts.find(map_id) !=
+ expected_map_refcounts.end());
+ ASSERT_EQ(expected_map_refcounts[map_id], ref_count);
+ // Mark the map as existing.
+ expected_map_refcounts.erase(map_id);
+ found_map_ids.insert(map_id);
+ ++valid_keys;
+ } else if (IsMapValueKey(it->first, &map_id)) {
+ ASSERT_TRUE(found_map_ids.find(map_id) != found_map_ids.end());
+ ++valid_keys;
+ }
+ }
+ // Check that all maps referred to exist.
+ ASSERT_TRUE(expected_map_refcounts.empty());
+
+ // Count valid keys.
+ if (data.find(next_namespace_id_key) != data.end())
+ ++valid_keys;
+
+ if (data.find(next_map_id_key) != data.end())
+ ++valid_keys;
+
+ ASSERT_EQ(data.size(), valid_keys);
+}
+
+void SessionStorageDatabaseTest::CheckEmptyDatabase() const {
+ DataMap data;
+ ReadData(&data);
+ size_t valid_keys = 0;
+ if (data.find(SessionStorageDatabase::NamespacePrefix()) != data.end())
+ ++valid_keys;
+ if (data.find(SessionStorageDatabase::NextNamespaceIdKey()) != data.end())
+ ++valid_keys;
+ if (data.find(SessionStorageDatabase::NextMapIdKey()) != data.end())
+ ++valid_keys;
+ EXPECT_EQ(valid_keys, data.size());
+}
+
+void SessionStorageDatabaseTest::DumpData() const {
+ LOG(WARNING) << "---- Session storage contents";
+ scoped_ptr<leveldb::Iterator> it(
+ db_->db_->NewIterator(leveldb::ReadOptions()));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ int64 dummy_map_id;
+ if (IsMapValueKey(it->key().ToString(), &dummy_map_id)) {
+ // Convert the value back to string16.
+ string16 value;
+ size_t len = it->value().size() / sizeof(char16);
+ value.resize(len);
+ value.assign(reinterpret_cast<const char16*>(it->value().data()), len);
+ LOG(WARNING) << it->key().ToString() << ": " << value;
+ } else {
+ LOG(WARNING) << it->key().ToString() << ": " << it->value().ToString();
+ }
+ }
+ LOG(WARNING) << "----";
+}
+
+void SessionStorageDatabaseTest::CheckAreaData(
+ int64 namespace_id, const GURL& origin, const ValuesMap& reference) const {
+ ValuesMap values;
+ db_->ReadAreaValues(namespace_id, origin, &values);
+ CompareValuesMaps(values, reference);
+}
+
+void SessionStorageDatabaseTest::CompareValuesMaps(
+ const ValuesMap& map1,
+ const ValuesMap& map2) const {
+ ASSERT_EQ(map2.size(), map1.size());
+ for (ValuesMap::const_iterator it = map1.begin(); it != map1.end(); ++it) {
+ string16 key = it->first;
+ ASSERT_TRUE(map2.find(key) != map2.end());
+ NullableString16 val1 = it->second;
+ NullableString16 val2 = map2.find(key)->second;
+ EXPECT_EQ(val2.is_null(), val1.is_null());
+ EXPECT_EQ(val2.string(), val1.string());
+ }
+}
+
+std::string SessionStorageDatabaseTest::GetMapForArea(
+ int64 namespace_id, const GURL& origin) const {
+ bool exists;
+ std::string map_id;
+ EXPECT_TRUE(db_->GetMapForArea(namespace_id, origin,
+ &exists, &map_id));
+ EXPECT_TRUE(exists);
+ return map_id;
+}
+
+int64 SessionStorageDatabaseTest::GetMapRefCount(
+ const std::string& map_id) const {
+ int64 ref_count;
+ EXPECT_TRUE(db_->GetMapRefCount(map_id, &ref_count));
+ return ref_count;
+}
+
+int64 SessionStorageDatabaseTest::NextNamespaceId() const {
+ int64 next_namespace_id;
+ EXPECT_TRUE(db_->GetNextNamespaceId(&next_namespace_id));
+ return next_namespace_id;
+}
+
+TEST_F(SessionStorageDatabaseTest, EmptyDatabaseSanityCheck) {
+ // An empty database should be valid.
+ CheckDatabaseConsistency();
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteDataForOneOrigin) {
+ // Keep track on what the values should look like.
+ ValuesMap reference;
+ // Write data.
+ {
+ ValuesMap changes;
+ changes[kKey1] = kValue1;
+ changes[kKey2] = kValue2;
+ changes[kKey3] = kValue3;
+ reference[kKey1] = kValue1;
+ reference[kKey2] = kValue2;
+ reference[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, changes));
+ }
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, reference);
+
+ // Overwrite and delete values.
+ {
+ ValuesMap changes;
+ changes[kKey1] = kValue4;
+ changes[kKey3] = kValueNull;
+ reference[kKey1] = kValue4;
+ reference.erase(kKey3);
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, changes));
+ }
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, reference);
+
+ // Clear data before writing.
+ {
+ ValuesMap changes;
+ changes[kKey2] = kValue2;
+ reference.erase(kKey1);
+ reference[kKey2] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, true, changes));
+ }
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, reference);
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteDataForTwoOrigins) {
+ // Write data.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+
+ ValuesMap data2;
+ data2[kKey1] = kValue4;
+ data2[kKey2] = kValue1;
+ data2[kKey3] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, data1);
+ CheckAreaData(1, kOrigin2, data2);
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteDataForTwoNamespaces) {
+ // Write data.
+ ValuesMap data11;
+ data11[kKey1] = kValue1;
+ data11[kKey2] = kValue2;
+ data11[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data11));
+ ValuesMap data12;
+ data12[kKey2] = kValue4;
+ data12[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data12));
+ ValuesMap data21;
+ data21[kKey1] = kValue2;
+ data21[kKey2] = kValue4;
+ EXPECT_TRUE(db_->CommitAreaChanges(2, kOrigin1, false, data21));
+ ValuesMap data22;
+ data22[kKey2] = kValue1;
+ data22[kKey3] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(2, kOrigin2, false, data22));
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, data11);
+ CheckAreaData(1, kOrigin2, data12);
+ CheckAreaData(2, kOrigin1, data21);
+ CheckAreaData(2, kOrigin2, data22);
+}
+
+TEST_F(SessionStorageDatabaseTest, ShallowCopy) {
+ // Write data for a namespace, for 2 origins.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ ValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+ // Make a shallow copy.
+ EXPECT_TRUE(db_->CloneNamespace(1, 5));
+ // Now both namespaces should have the same data.
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, data1);
+ CheckAreaData(1, kOrigin2, data2);
+ CheckAreaData(5, kOrigin1, data1);
+ CheckAreaData(5, kOrigin2, data2);
+ // Both the namespaces refer to the same maps.
+ EXPECT_EQ(GetMapForArea(1, kOrigin1), GetMapForArea(5, kOrigin1));
+ EXPECT_EQ(GetMapForArea(1, kOrigin2), GetMapForArea(5, kOrigin2));
+ EXPECT_EQ(2, GetMapRefCount(GetMapForArea(1, kOrigin1)));
+ EXPECT_EQ(2, GetMapRefCount(GetMapForArea(1, kOrigin2)));
+}
+
+TEST_F(SessionStorageDatabaseTest, DeepCopy) {
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ EXPECT_TRUE(db_->CloneNamespace(1, 5));
+
+ // Make the shallow copy deep.
+ EXPECT_TRUE(db_->DeepCopyArea(5, kOrigin1));
+
+ // Write data into the deep copy.
+ ValuesMap changes;
+ ValuesMap reference;
+ changes[kKey1] = kValueNull;
+ changes[kKey2] = kValue4;
+ changes[kKey3] = kValue4;
+ reference[kKey2] = kValue4;
+ reference[kKey3] = kValue4;
+ EXPECT_TRUE(db_->CommitAreaChanges(5, kOrigin1, false, changes));
+
+ // Values in the original namespace were not changed.
+ CheckAreaData(1, kOrigin1, data1);
+ // But values in the deep copy were.
+ CheckAreaData(5, kOrigin1, reference);
+
+ // The namespaces no longer refer to the same map.
+ EXPECT_NE(GetMapForArea(1, kOrigin1), GetMapForArea(5, kOrigin1));
+ EXPECT_EQ(1, GetMapRefCount(GetMapForArea(1, kOrigin1)));
+ EXPECT_EQ(1, GetMapRefCount(GetMapForArea(5, kOrigin1)));
+}
+
+TEST_F(SessionStorageDatabaseTest, ManyShallowCopies) {
+ // Write data for a namespace, for 2 origins.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ ValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+
+ // Make a two shallow copies.
+ EXPECT_TRUE(db_->CloneNamespace(1, 5));
+ EXPECT_TRUE(db_->CloneNamespace(1, 6));
+
+ // Make a shallow copy of a shallow copy.
+ EXPECT_TRUE(db_->CloneNamespace(6, 7));
+
+ // Now all namespaces should have the same data.
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, data1);
+ CheckAreaData(5, kOrigin1, data1);
+ CheckAreaData(6, kOrigin1, data1);
+ CheckAreaData(7, kOrigin1, data1);
+ CheckAreaData(1, kOrigin2, data2);
+ CheckAreaData(5, kOrigin2, data2);
+ CheckAreaData(6, kOrigin2, data2);
+ CheckAreaData(7, kOrigin2, data2);
+
+ // All namespaces refer to the same maps.
+ EXPECT_EQ(GetMapForArea(1, kOrigin1), GetMapForArea(5, kOrigin1));
+ EXPECT_EQ(GetMapForArea(1, kOrigin2), GetMapForArea(5, kOrigin2));
+ EXPECT_EQ(GetMapForArea(1, kOrigin1), GetMapForArea(6, kOrigin1));
+ EXPECT_EQ(GetMapForArea(1, kOrigin2), GetMapForArea(6, kOrigin2));
+ EXPECT_EQ(GetMapForArea(1, kOrigin1), GetMapForArea(7, kOrigin1));
+ EXPECT_EQ(GetMapForArea(1, kOrigin2), GetMapForArea(7, kOrigin2));
+
+ // Check the ref counts.
+ EXPECT_EQ(4, GetMapRefCount(GetMapForArea(1, kOrigin1)));
+ EXPECT_EQ(4, GetMapRefCount(GetMapForArea(1, kOrigin2)));
+}
+
+TEST_F(SessionStorageDatabaseTest, DisassociateShallowCopy) {
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ EXPECT_TRUE(db_->CloneNamespace(1, 5));
+
+ // Disassoaciate the shallow copy.
+ EXPECT_TRUE(db_->DeleteArea(5, kOrigin1));
+ CheckDatabaseConsistency();
+
+ // Now new data can be written to that map.
+ ValuesMap reference;
+ ValuesMap changes;
+ changes[kKey1] = kValueNull;
+ changes[kKey2] = kValue4;
+ changes[kKey3] = kValue4;
+ reference[kKey2] = kValue4;
+ reference[kKey3] = kValue4;
+ EXPECT_TRUE(db_->CommitAreaChanges(5, kOrigin1, false, changes));
+
+ // Values in the original map were not changed.
+ CheckAreaData(1, kOrigin1, data1);
+
+ // But values in the disassociated map were.
+ CheckAreaData(5, kOrigin1, reference);
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteNamespace) {
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ ValuesMap data2;
+ data2[kKey2] = kValue4;
+ data2[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+ EXPECT_TRUE(db_->DeleteNamespace(1));
+ CheckDatabaseConsistency();
+ CheckEmptyDatabase();
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteNamespaceWithShallowCopy) {
+ // Write data for a namespace, for 2 origins.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ ValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+
+ // Make a shallow copy and delete the original namespace.
+ EXPECT_TRUE(db_->CloneNamespace(1, 5));;
+ EXPECT_TRUE(db_->DeleteNamespace(1));
+
+ // The original namespace has no data.
+ CheckDatabaseConsistency();
+ CheckAreaData(1, kOrigin1, ValuesMap());
+ CheckAreaData(1, kOrigin2, ValuesMap());
+ // But the copy persists.
+ CheckAreaData(5, kOrigin1, data1);
+ CheckAreaData(5, kOrigin2, data2);
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteArea) {
+ // Write data for a namespace, for 2 origins.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ ValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+
+ EXPECT_TRUE(db_->DeleteArea(1, kOrigin2));
+ CheckDatabaseConsistency();
+ // The data for the non-deleted origin persists.
+ CheckAreaData(1, kOrigin1, data1);
+ // The data for the deleted origin is gone.
+ CheckAreaData(1, kOrigin2, ValuesMap());
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteAreaWithShallowCopy) {
+ // Write data for a namespace, for 2 origins.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ ValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin2, false, data2));
+
+ // Make a shallow copy and delete an origin from the original namespace.
+ EXPECT_TRUE(db_->CloneNamespace(1, 5));
+ EXPECT_TRUE(db_->DeleteArea(1, kOrigin1));
+ CheckDatabaseConsistency();
+
+ // The original namespace has data for only the non-deleted origin.
+ CheckAreaData(1, kOrigin1, ValuesMap());
+ CheckAreaData(1, kOrigin2, data2);
+ // But the copy persists.
+ CheckAreaData(5, kOrigin1, data1);
+ CheckAreaData(5, kOrigin2, data2);
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteRawBytes) {
+ // Write data which is not valid utf8 and contains null bytes.
+ unsigned char raw_data[10] = {255, 0, 0, 0, 1, 2, 3, 4, 5, 0};
+ ValuesMap changes;
+ string16 string_with_raw_data;
+ string_with_raw_data.assign(reinterpret_cast<char16*>(raw_data), 5);
+ changes[kKey1] = NullableString16(string_with_raw_data, false);
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, changes));
+ CheckDatabaseConsistency();
+ ValuesMap values;
+ db_->ReadAreaValues(1, kOrigin1, &values);
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(values[kKey1].string().data());
+ for (int i = 0; i < 10; ++i)
+ EXPECT_EQ(raw_data[i], data[i]);
+}
+
+TEST_F(SessionStorageDatabaseTest, NextNamespaceId) {
+ // Create namespaces, check the next namespace id.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(10, kOrigin1, false, data1));
+ EXPECT_EQ(10 + 1, NextNamespaceId());
+ ASSERT_TRUE(db_->CommitAreaChanges(343, kOrigin1, false, data1));
+ EXPECT_EQ(343 + 1, NextNamespaceId());
+ ASSERT_TRUE(db_->CommitAreaChanges(99, kOrigin1, false, data1));
+ EXPECT_EQ(343 + 1, NextNamespaceId());
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // The next namespace id is persisted.
+ EXPECT_EQ(344, NextNamespaceId());
+
+ // Create more namespaces.
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+ EXPECT_EQ(344 + 1 + 1, NextNamespaceId());
+
+ EXPECT_TRUE(db_->CommitAreaChanges(959, kOrigin1, false, data1));
+ EXPECT_EQ(344 + 959 + 1, NextNamespaceId());
+}
+
+TEST_F(SessionStorageDatabaseTest, NamespaceOffset) {
+ // Create a namespace with id 1.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // Create another namespace with id 1.
+ ValuesMap data2;
+ data2[kKey1] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data2));
+
+ // Now the values for namespace 1 are the new ones.
+ CheckAreaData(1, kOrigin1, data2);
+
+ // The values for the old namespace 1 are still accessible via id -1.
+ CheckAreaData(-1, kOrigin1, data1);
+}
+
+TEST_F(SessionStorageDatabaseTest, NamespaceOffsetCloneNamespace) {
+ // Create a namespace with id 1.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // Create another namespace with id 1.
+ ValuesMap data2;
+ data2[kKey1] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data2));
+
+ // Make a shallow copy of the newly created namespace.
+ EXPECT_TRUE(db_->CloneNamespace(1, 20));
+
+ // The clone contains values from the newly created namespace.
+ CheckAreaData(20, kOrigin1, data2);
+ CheckAreaData(1, kOrigin1, data2);
+
+ // The values for the old namespace 1 are still accessible via id -1.
+ CheckAreaData(-1, kOrigin1, data1);
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // The namespace and the clone are still accessible.
+ CheckAreaData(-1, kOrigin1, data2);
+ CheckAreaData(-20, kOrigin1, data2);
+ CheckAreaData(-22, kOrigin1, data1);
+}
+
+TEST_F(SessionStorageDatabaseTest, NamespaceOffsetDeepCopyArea) {
+ // Create a namespace with id 1.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // Create another namespace with id 1.
+ ValuesMap data2;
+ data2[kKey1] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data2));
+
+ // Make a shallow copy of the newly created namespace.
+ EXPECT_TRUE(db_->CloneNamespace(1, 20));
+ // And make it deep.
+ EXPECT_TRUE(db_->DeepCopyArea(20, kOrigin1));
+
+ // Now the values can be altered in the deep copy.
+ ValuesMap data3;
+ data3[kKey1] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(20, kOrigin1, false, data3));
+
+ CheckAreaData(20, kOrigin1, data3);
+ CheckAreaData(1, kOrigin1, data2);
+
+ // The values for the old namespace 1 are still accessible via id -1.
+ CheckAreaData(-1, kOrigin1, data1);
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // The namespace and the deep copy are still accessible.
+ CheckAreaData(-1, kOrigin1, data3);
+ CheckAreaData(-20, kOrigin1, data2);
+ CheckAreaData(-22, kOrigin1, data1);
+}
+
+TEST_F(SessionStorageDatabaseTest, NamespaceOffsetDeleteArea) {
+ // Create a namespace with id 1.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // Create another namespace with id 1.
+ ValuesMap data2;
+ data2[kKey1] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data2));
+
+ // Delete kOrigin1 from the newly created namespace.
+ EXPECT_TRUE(db_->DeleteArea(1, kOrigin1));
+
+ // Namespace 1 is empty.
+ CheckAreaData(1, kOrigin1, ValuesMap());
+
+ // The values for the old namespace 1 are still accessible via id -1.
+ CheckAreaData(-1, kOrigin1, data1);
+}
+
+TEST_F(SessionStorageDatabaseTest, NamespaceOffsetDeleteNamespace) {
+ // Create a namespace with id 1.
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data1));
+
+ // Close the database and recreate it.
+ ResetDatabase();
+
+ // Create another namespace with id 1.
+ ValuesMap data2;
+ data2[kKey1] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(1, kOrigin1, false, data2));
+
+ // Delete the newly created namespace.
+ EXPECT_TRUE(db_->DeleteNamespace(1));
+
+ // Namespace 1 is empty.
+ CheckAreaData(1, kOrigin1, ValuesMap());
+
+ // The values for the old namespace 1 are still accessible via id -1.
+ CheckAreaData(-1, kOrigin1, data1);
+}
+
+} // namespace dom_storage
diff --git a/webkit/dom_storage/webkit_dom_storage.gypi b/webkit/dom_storage/webkit_dom_storage.gypi
index 65df554..82f6670 100644
--- a/webkit/dom_storage/webkit_dom_storage.gypi
+++ b/webkit/dom_storage/webkit_dom_storage.gypi
@@ -11,6 +11,7 @@
'dependencies': [
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/sql/sql.gyp:sql',
+ '<(DEPTH)/third_party/leveldatabase/leveldatabase.gyp:leveldatabase',
'<(DEPTH)/third_party/sqlite/sqlite.gyp:sqlite',
'<(DEPTH)/webkit/support/webkit_support.gyp:quota',
],
@@ -32,6 +33,8 @@
'dom_storage_task_runner.cc',
'dom_storage_task_runner.h',
'dom_storage_types.h',
+ 'session_storage_database.cc',
+ 'session_storage_database.h'
],
'conditions': [
['inside_chromium_build==0', {