summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authormarja@chromium.org <marja@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-07 09:23:52 +0000
committermarja@chromium.org <marja@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-07 09:23:52 +0000
commit4d63bbd89567a40c8eb44098ee0bb4a9447881fc (patch)
treeb4c523b2da96a50fec0874117525de9cea756d9b /webkit
parentaed59fd7010f5e34598cfd9acacf01742b7f04df (diff)
downloadchromium_src-4d63bbd89567a40c8eb44098ee0bb4a9447881fc.zip
chromium_src-4d63bbd89567a40c8eb44098ee0bb4a9447881fc.tar.gz
chromium_src-4d63bbd89567a40c8eb44098ee0bb4a9447881fc.tar.bz2
Better session restore: Persist sessionStorage on disk.
Write sessionStorage on disk, and restore it after relaunching, when recovering from a crash, and when the startup option "continue where I left off" is selected. BUG=104292 TEST=Manual; SessionStorageDatabaseTest.ReadOriginsInNamespace Review URL: https://chromiumcodereview.appspot.com/9963107 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150314 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/dom_storage/dom_storage_area.cc34
-rw-r--r--webkit/dom_storage/dom_storage_area.h5
-rw-r--r--webkit/dom_storage/dom_storage_area_unittest.cc6
-rw-r--r--webkit/dom_storage/dom_storage_context.cc166
-rw-r--r--webkit/dom_storage/dom_storage_context.h28
-rw-r--r--webkit/dom_storage/dom_storage_namespace.cc23
-rw-r--r--webkit/dom_storage/dom_storage_namespace.h7
-rw-r--r--webkit/dom_storage/session_storage_database.cc57
-rw-r--r--webkit/dom_storage/session_storage_database.h10
-rw-r--r--webkit/dom_storage/session_storage_database_unittest.cc51
10 files changed, 334 insertions, 53 deletions
diff --git a/webkit/dom_storage/dom_storage_area.cc b/webkit/dom_storage/dom_storage_area.cc
index a532194..734edff 100644
--- a/webkit/dom_storage/dom_storage_area.cc
+++ b/webkit/dom_storage/dom_storage_area.cc
@@ -15,6 +15,8 @@
#include "webkit/dom_storage/dom_storage_task_runner.h"
#include "webkit/dom_storage/dom_storage_types.h"
#include "webkit/dom_storage/local_storage_database_adapter.h"
+#include "webkit/dom_storage/session_storage_database.h"
+#include "webkit/dom_storage/session_storage_database_adapter.h"
#include "webkit/fileapi/file_system_util.h"
#include "webkit/glue/webkit_glue.h"
@@ -72,16 +74,23 @@ DomStorageArea::DomStorageArea(
int64 namespace_id,
const std::string& persistent_namespace_id,
const GURL& origin,
+ SessionStorageDatabase* session_storage_backing,
DomStorageTaskRunner* task_runner)
: namespace_id_(namespace_id),
persistent_namespace_id_(persistent_namespace_id),
origin_(origin),
task_runner_(task_runner),
map_(new DomStorageMap(kPerAreaQuota + kPerAreaOverQuotaAllowance)),
+ session_storage_backing_(session_storage_backing),
is_initial_import_done_(true),
is_shutdown_(false),
commit_batches_in_flight_(0) {
DCHECK(namespace_id != kLocalStorageNamespaceId);
+ if (session_storage_backing) {
+ backing_.reset(new SessionStorageDatabaseAdapter(
+ session_storage_backing, persistent_namespace_id, origin));
+ is_initial_import_done_ = false;
+ }
}
DomStorageArea::~DomStorageArea() {
@@ -168,13 +177,20 @@ DomStorageArea* DomStorageArea::ShallowCopy(
const std::string& destination_persistent_namespace_id) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id);
- DCHECK(!backing_.get()); // SessionNamespaces aren't stored on disk.
DomStorageArea* copy = new DomStorageArea(
destination_namespace_id, destination_persistent_namespace_id, origin_,
- task_runner_);
+ session_storage_backing_, task_runner_);
copy->map_ = map_;
copy->is_shutdown_ = is_shutdown_;
+ copy->is_initial_import_done_ = true;
+
+ // All the uncommitted changes to this area need to happen before the actual
+ // shallow copy is made (scheduled by the upper layer). Another OnCommitTimer
+ // call might be in the event queue at this point, but it's handled gracefully
+ // when it fires.
+ if (commit_batch_.get())
+ OnCommitTimer();
return copy;
}
@@ -185,6 +201,8 @@ bool DomStorageArea::HasUncommittedChanges() const {
void DomStorageArea::DeleteOrigin() {
DCHECK(!is_shutdown_);
+ // This function shouldn't be called for sessionStorage.
+ DCHECK(!session_storage_backing_.get());
if (HasUncommittedChanges()) {
// TODO(michaeln): This logically deletes the data immediately,
// and in a matter of a second, deletes the rows from the backing
@@ -236,7 +254,6 @@ void DomStorageArea::InitialImportIfNeeded() {
if (is_initial_import_done_)
return;
- DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_);
DCHECK(backing_.get());
ValuesMap initial_values;
@@ -264,13 +281,15 @@ DomStorageArea::CommitBatch* DomStorageArea::CreateCommitBatchIfNeeded() {
}
void DomStorageArea::OnCommitTimer() {
- DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_);
if (is_shutdown_)
return;
DCHECK(backing_.get());
- DCHECK(commit_batch_.get());
- DCHECK(!commit_batches_in_flight_);
+
+ // It's possible that there is nothing to commit, since a shallow copy occured
+ // before the timer fired.
+ if (!commit_batch_.get())
+ return;
// This method executes on the primary sequence, we schedule
// a task for immediate execution on the commit sequence.
@@ -298,9 +317,9 @@ void DomStorageArea::CommitChanges(const CommitBatch* commit_batch) {
void DomStorageArea::OnCommitComplete() {
// We're back on the primary sequence in this method.
DCHECK(task_runner_->IsRunningOnPrimarySequence());
+ --commit_batches_in_flight_;
if (is_shutdown_)
return;
- --commit_batches_in_flight_;
if (commit_batch_.get() && !commit_batches_in_flight_) {
// More changes have accrued, restart the timer.
task_runner_->PostDelayedTask(
@@ -323,6 +342,7 @@ void DomStorageArea::ShutdownInCommitSequence() {
}
commit_batch_.reset();
backing_.reset();
+ session_storage_backing_ = NULL;
}
} // namespace dom_storage
diff --git a/webkit/dom_storage/dom_storage_area.h b/webkit/dom_storage/dom_storage_area.h
index 1dbf31c..7a17901 100644
--- a/webkit/dom_storage/dom_storage_area.h
+++ b/webkit/dom_storage/dom_storage_area.h
@@ -19,6 +19,7 @@ namespace dom_storage {
class DomStorageDatabaseAdapter;
class DomStorageMap;
class DomStorageTaskRunner;
+class SessionStorageDatabase;
// Container for a per-origin Map of key/value pairs potentially
// backed by storage on disk and lazily commits changes to disk.
@@ -36,10 +37,11 @@ class DomStorageArea
const FilePath& directory,
DomStorageTaskRunner* task_runner);
- // Session storage.
+ // Session storage. Backed on disk if |session_storage_backing| is not NULL.
DomStorageArea(int64 namespace_id,
const std::string& persistent_namespace_id,
const GURL& origin,
+ SessionStorageDatabase* session_storage_backing,
DomStorageTaskRunner* task_runner);
const GURL& origin() const { return origin_; }
@@ -119,6 +121,7 @@ class DomStorageArea
scoped_refptr<DomStorageTaskRunner> task_runner_;
scoped_refptr<DomStorageMap> map_;
scoped_ptr<DomStorageDatabaseAdapter> backing_;
+ scoped_refptr<SessionStorageDatabase> session_storage_backing_;
bool is_initial_import_done_;
bool is_shutdown_;
scoped_ptr<CommitBatch> commit_batch_;
diff --git a/webkit/dom_storage/dom_storage_area_unittest.cc b/webkit/dom_storage/dom_storage_area_unittest.cc
index e41b080..5d2e953 100644
--- a/webkit/dom_storage/dom_storage_area_unittest.cc
+++ b/webkit/dom_storage/dom_storage_area_unittest.cc
@@ -73,7 +73,7 @@ class DomStorageAreaTest : public testing::Test {
TEST_F(DomStorageAreaTest, DomStorageAreaBasics) {
scoped_refptr<DomStorageArea> area(
- new DomStorageArea(1, std::string(), kOrigin, NULL));
+ new DomStorageArea(1, std::string(), kOrigin, NULL, NULL));
string16 old_value;
NullableString16 old_nullable_value;
scoped_refptr<DomStorageArea> copy;
@@ -138,12 +138,12 @@ TEST_F(DomStorageAreaTest, BackingDatabaseOpened) {
EXPECT_FALSE(file_util::PathExists(kExpectedOriginFilePath));
}
- // Valid directory and origin but non-local namespace id. Backing should
+ // Valid directory and origin but no session storage backing. Backing should
// be null.
{
scoped_refptr<DomStorageArea> area(
new DomStorageArea(kSessionStorageNamespaceId, std::string(), kOrigin,
- NULL));
+ NULL, NULL));
EXPECT_EQ(NULL, area->backing_.get());
EXPECT_TRUE(area->is_initial_import_done_);
diff --git a/webkit/dom_storage/dom_storage_context.cc b/webkit/dom_storage/dom_storage_context.cc
index 9d431ba..4e46c89 100644
--- a/webkit/dom_storage/dom_storage_context.cc
+++ b/webkit/dom_storage/dom_storage_context.cc
@@ -15,12 +15,15 @@
#include "webkit/dom_storage/dom_storage_namespace.h"
#include "webkit/dom_storage/dom_storage_task_runner.h"
#include "webkit/dom_storage/dom_storage_types.h"
+#include "webkit/dom_storage/session_storage_database.h"
#include "webkit/quota/special_storage_policy.h"
using file_util::FileEnumerator;
namespace dom_storage {
+static const int kSessionStoraceScavengingSeconds = 60;
+
DomStorageContext::UsageInfo::UsageInfo() : data_size(0) {}
DomStorageContext::UsageInfo::~UsageInfo() {}
@@ -34,7 +37,8 @@ DomStorageContext::DomStorageContext(
task_runner_(task_runner),
is_shutdown_(false),
force_keep_session_state_(false),
- special_storage_policy_(special_storage_policy) {
+ special_storage_policy_(special_storage_policy),
+ scavenging_started_(false) {
// AtomicSequenceNum starts at 0 but we want to start session
// namespace ids at one since zero is reserved for the
// kLocalStorageNamespaceId.
@@ -88,6 +92,7 @@ void DomStorageContext::GetUsageInfo(std::vector<UsageInfo>* infos,
infos->push_back(info);
}
}
+ // TODO(marja): Get usage infos for sessionStorage (crbug.com/123599).
}
void DomStorageContext::DeleteOrigin(const GURL& origin) {
@@ -111,7 +116,7 @@ void DomStorageContext::Shutdown() {
for (; it != namespaces_.end(); ++it)
it->second->Shutdown();
- if (localstorage_directory_.empty())
+ if (localstorage_directory_.empty() && !session_storage_database_.get())
return;
// Respect the content policy settings about what to
@@ -186,13 +191,31 @@ void DomStorageContext::CreateSessionNamespace(
DCHECK(namespace_id != kLocalStorageNamespaceId);
DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
namespaces_[namespace_id] = new DomStorageNamespace(
- namespace_id, persistent_namespace_id, task_runner_);
+ namespace_id, persistent_namespace_id, session_storage_database_.get(),
+ task_runner_);
}
void DomStorageContext::DeleteSessionNamespace(
int64 namespace_id, bool should_persist_data) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
- // TODO(marja): Protect the sessionStorage data (once it's written on disk).
+ StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
+ if (it == namespaces_.end())
+ return;
+ if (session_storage_database_.get()) {
+ std::string persistent_namespace_id = it->second->persistent_namespace_id();
+ if (!should_persist_data) {
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DomStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(
+ base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
+ session_storage_database_,
+ persistent_namespace_id));
+ } else if (!scavenging_started_) {
+ // Protect the persistent namespace ID from scavenging.
+ protected_persistent_session_ids_.insert(persistent_namespace_id);
+ }
+ }
namespaces_.erase(namespace_id);
}
@@ -211,23 +234,124 @@ void DomStorageContext::CloneSessionNamespace(
}
void DomStorageContext::ClearSessionOnlyOrigins() {
- std::vector<UsageInfo> infos;
- const bool kDontIncludeFileInfo = false;
- GetUsageInfo(&infos, kDontIncludeFileInfo);
- for (size_t i = 0; i < infos.size(); ++i) {
- const GURL& origin = infos[i].origin;
- if (special_storage_policy_->IsStorageProtected(origin))
- continue;
- if (!special_storage_policy_->IsStorageSessionOnly(origin))
- continue;
-
- const bool kNotRecursive = false;
- FilePath database_file_path = localstorage_directory_.Append(
- DomStorageArea::DatabaseFileNameFromOrigin(origin));
- file_util::Delete(database_file_path, kNotRecursive);
- file_util::Delete(
- DomStorageDatabase::GetJournalFilePath(database_file_path),
- kNotRecursive);
+ if (!localstorage_directory_.empty()) {
+ std::vector<UsageInfo> infos;
+ const bool kDontIncludeFileInfo = false;
+ GetUsageInfo(&infos, kDontIncludeFileInfo);
+ for (size_t i = 0; i < infos.size(); ++i) {
+ const GURL& origin = infos[i].origin;
+ if (special_storage_policy_->IsStorageProtected(origin))
+ continue;
+ if (!special_storage_policy_->IsStorageSessionOnly(origin))
+ continue;
+
+ const bool kNotRecursive = false;
+ FilePath database_file_path = localstorage_directory_.Append(
+ DomStorageArea::DatabaseFileNameFromOrigin(origin));
+ file_util::Delete(database_file_path, kNotRecursive);
+ file_util::Delete(
+ DomStorageDatabase::GetJournalFilePath(database_file_path),
+ kNotRecursive);
+ }
+ }
+ if (session_storage_database_.get()) {
+ std::vector<std::string> namespace_ids;
+ session_storage_database_->ReadNamespaceIds(&namespace_ids);
+ for (std::vector<std::string>::const_iterator it = namespace_ids.begin();
+ it != namespace_ids.end(); ++it) {
+ std::vector<GURL> origins;
+ session_storage_database_->ReadOriginsInNamespace(*it, &origins);
+
+ for (std::vector<GURL>::const_iterator origin_it = origins.begin();
+ origin_it != origins.end(); ++origin_it) {
+ if (special_storage_policy_->IsStorageProtected(*origin_it))
+ continue;
+ if (!special_storage_policy_->IsStorageSessionOnly(*origin_it))
+ continue;
+ session_storage_database_->DeleteArea(*it, *origin_it);
+ }
+ }
+ }
+}
+
+void DomStorageContext::SetSaveSessionStorageOnDisk() {
+ DCHECK(namespaces_.empty());
+ if (!sessionstorage_directory_.empty()) {
+ session_storage_database_ = new SessionStorageDatabase(
+ sessionstorage_directory_);
+ }
+}
+
+void DomStorageContext::StartScavengingUnusedSessionStorage() {
+ if (session_storage_database_.get()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(&DomStorageContext::FindUnusedNamespaces, this),
+ base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
+ }
+}
+
+void DomStorageContext::FindUnusedNamespaces() {
+ DCHECK(session_storage_database_.get());
+ DCHECK(!scavenging_started_);
+ scavenging_started_ = true;
+ std::set<std::string> namespace_ids_in_use;
+ for (StorageNamespaceMap::const_iterator it = namespaces_.begin();
+ it != namespaces_.end(); ++it)
+ namespace_ids_in_use.insert(it->second->persistent_namespace_id());
+ std::set<std::string> protected_persistent_session_ids;
+ protected_persistent_session_ids.swap(protected_persistent_session_ids_);
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE, DomStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(
+ &DomStorageContext::FindUnusedNamespacesInCommitSequence,
+ this, namespace_ids_in_use, protected_persistent_session_ids));
+}
+
+void DomStorageContext::FindUnusedNamespacesInCommitSequence(
+ const std::set<std::string>& namespace_ids_in_use,
+ const std::set<std::string>& protected_persistent_session_ids) {
+ DCHECK(session_storage_database_.get());
+ // Delete all namespaces which don't have an associated DomStorageNamespace
+ // alive.
+ std::vector<std::string> namespace_ids;
+ session_storage_database_->ReadNamespaceIds(&namespace_ids);
+ for (std::vector<std::string>::const_iterator it = namespace_ids.begin();
+ it != namespace_ids.end(); ++it) {
+ if (namespace_ids_in_use.find(*it) == namespace_ids_in_use.end() &&
+ protected_persistent_session_ids.find(*it) ==
+ protected_persistent_session_ids.end()) {
+ deletable_persistent_namespace_ids_.push_back(*it);
+ }
+ }
+ if (!deletable_persistent_namespace_ids_.empty()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(
+ &DomStorageContext::DeleteNextUnusedNamespace,
+ this),
+ base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
+ }
+}
+
+void DomStorageContext::DeleteNextUnusedNamespace() {
+ if (is_shutdown_)
+ return;
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE, DomStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(
+ &DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence,
+ this));
+}
+
+void DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence() {
+ const std::string& persistent_id = deletable_persistent_namespace_ids_.back();
+ session_storage_database_->DeleteNamespace(persistent_id);
+ deletable_persistent_namespace_ids_.pop_back();
+ if (!deletable_persistent_namespace_ids_.empty()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(
+ &DomStorageContext::DeleteNextUnusedNamespace,
+ this),
+ base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
}
}
diff --git a/webkit/dom_storage/dom_storage_context.h b/webkit/dom_storage/dom_storage_context.h
index 5860ec9..d2b94d4 100644
--- a/webkit/dom_storage/dom_storage_context.h
+++ b/webkit/dom_storage/dom_storage_context.h
@@ -6,6 +6,7 @@
#define WEBKIT_DOM_STORAGE_DOM_STORAGE_CONTEXT_H_
#include <map>
+#include <set>
#include <vector>
#include "base/atomic_sequence_num.h"
@@ -34,6 +35,7 @@ class DomStorageArea;
class DomStorageNamespace;
class DomStorageSession;
class DomStorageTaskRunner;
+class SessionStorageDatabase;
// The Context is the root of an object containment hierachy for
// Namespaces and Areas related to the owning profile.
@@ -159,6 +161,15 @@ class DomStorageContext
void CloneSessionNamespace(int64 existing_id, int64 new_id,
const std::string& new_persistent_id);
+ // Starts backing sessionStorage on disk. This function must be called right
+ // after DomStorageContext is created, before it's used.
+ void SetSaveSessionStorageOnDisk();
+
+ // Deletes all namespaces which don't have an associated DomStorageNamespace
+ // alive. This function is used for deleting possible leftover data after an
+ // unclean exit.
+ void StartScavengingUnusedSessionStorage();
+
private:
friend class DomStorageContextTest;
FRIEND_TEST_ALL_PREFIXES(DomStorageContextTest, Basics);
@@ -170,6 +181,14 @@ class DomStorageContext
void ClearSessionOnlyOrigins();
+ // For scavenging unused sessionStorages.
+ void FindUnusedNamespaces();
+ void FindUnusedNamespacesInCommitSequence(
+ const std::set<std::string>& namespace_ids_in_use,
+ const std::set<std::string>& protected_persistent_session_ids);
+ void DeleteNextUnusedNamespace();
+ void DeleteNextUnusedNamespaceInCommitSequence();
+
// Collection of namespaces keyed by id.
StorageNamespaceMap namespaces_;
@@ -194,6 +213,15 @@ class DomStorageContext
bool is_shutdown_;
bool force_keep_session_state_;
scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+ scoped_refptr<SessionStorageDatabase> session_storage_database_;
+
+ // For cleaning up unused namespaces gradually.
+ bool scavenging_started_;
+ std::vector<std::string> deletable_persistent_namespace_ids_;
+
+ // Persistent namespace IDs to protect from gradual deletion (they will
+ // be needed for session restore).
+ std::set<std::string> protected_persistent_session_ids_;
};
} // namespace dom_storage
diff --git a/webkit/dom_storage/dom_storage_namespace.cc b/webkit/dom_storage/dom_storage_namespace.cc
index 350444c..693954c 100644
--- a/webkit/dom_storage/dom_storage_namespace.cc
+++ b/webkit/dom_storage/dom_storage_namespace.cc
@@ -5,10 +5,13 @@
#include "webkit/dom_storage/dom_storage_namespace.h"
#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/location.h"
#include "base/logging.h"
#include "webkit/dom_storage/dom_storage_area.h"
#include "webkit/dom_storage/dom_storage_task_runner.h"
#include "webkit/dom_storage/dom_storage_types.h"
+#include "webkit/dom_storage/session_storage_database.h"
namespace dom_storage {
@@ -23,10 +26,12 @@ DomStorageNamespace::DomStorageNamespace(
DomStorageNamespace::DomStorageNamespace(
int64 namespace_id,
const std::string& persistent_namespace_id,
+ SessionStorageDatabase* session_storage_database,
DomStorageTaskRunner* task_runner)
: namespace_id_(namespace_id),
persistent_namespace_id_(persistent_namespace_id),
- task_runner_(task_runner) {
+ task_runner_(task_runner),
+ session_storage_database_(session_storage_database) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
}
@@ -43,7 +48,7 @@ DomStorageArea* DomStorageNamespace::OpenStorageArea(const GURL& origin) {
area = new DomStorageArea(origin, directory_, task_runner_);
} else {
area = new DomStorageArea(namespace_id_, persistent_namespace_id_, origin,
- task_runner_);
+ session_storage_database_, task_runner_);
}
areas_[origin] = AreaHolder(area, 1);
return area;
@@ -64,17 +69,29 @@ DomStorageNamespace* DomStorageNamespace::Clone(
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id);
DomStorageNamespace* clone = new DomStorageNamespace(
- clone_namespace_id, clone_persistent_namespace_id, task_runner_);
+ clone_namespace_id, clone_persistent_namespace_id,
+ session_storage_database_, task_runner_);
AreaMap::const_iterator it = areas_.begin();
+ // Clone the in-memory structures.
for (; it != areas_.end(); ++it) {
DomStorageArea* area = it->second.area_->ShallowCopy(
clone_namespace_id, clone_persistent_namespace_id);
clone->areas_[it->first] = AreaHolder(area, 0);
}
+ // And clone the on-disk structures, too.
+ if (session_storage_database_.get()) {
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DomStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace),
+ session_storage_database_.get(), persistent_namespace_id_,
+ clone_persistent_namespace_id));
+ }
return clone;
}
void DomStorageNamespace::DeleteOrigin(const GURL& origin) {
+ DCHECK(!session_storage_database_.get());
AreaHolder* holder = GetAreaHolder(origin);
if (holder) {
holder->area_->DeleteOrigin();
diff --git a/webkit/dom_storage/dom_storage_namespace.h b/webkit/dom_storage/dom_storage_namespace.h
index 7f2f37c..ecc0f92 100644
--- a/webkit/dom_storage/dom_storage_namespace.h
+++ b/webkit/dom_storage/dom_storage_namespace.h
@@ -17,6 +17,7 @@ namespace dom_storage {
class DomStorageArea;
class DomStorageTaskRunner;
+class SessionStorageDatabase;
// Container for the set of per-origin Areas.
// See class comments for DomStorageContext for a larger overview.
@@ -28,10 +29,11 @@ class DomStorageNamespace
DomStorageNamespace(const FilePath& directory, // may be empty
DomStorageTaskRunner* task_runner);
- // Constructor for a SessionStorage namespace with a non-zero id
- // and no backing directory on disk.
+ // Constructor for a SessionStorage namespace with a non-zero id and an
+ // optional backing on disk via |session_storage_database| (may be NULL).
DomStorageNamespace(int64 namespace_id,
const std::string& persistent_namespace_id,
+ SessionStorageDatabase* session_storage_database,
DomStorageTaskRunner* task_runner);
int64 namespace_id() const { return namespace_id_; }
@@ -79,6 +81,7 @@ class DomStorageNamespace
FilePath directory_;
AreaMap areas_;
scoped_refptr<DomStorageTaskRunner> task_runner_;
+ scoped_refptr<SessionStorageDatabase> session_storage_database_;
};
} // namespace dom_storage
diff --git a/webkit/dom_storage/session_storage_database.cc b/webkit/dom_storage/session_storage_database.cc
index 0f01db7..da3fb4b 100644
--- a/webkit/dom_storage/session_storage_database.cc
+++ b/webkit/dom_storage/session_storage_database.cc
@@ -52,12 +52,20 @@ void SessionStorageDatabase::ReadAreaValues(const std::string& namespace_id,
// nothing to be added to the result.
if (!LazyOpen(false))
return;
+
+ // While ReadAreaValues is in progress, another thread can call
+ // CommitAreaChanges. CommitAreaChanges might update map ref count key while
+ // this thread is iterating over the map ref count key. To protect the reading
+ // operation, create a snapshot and read from it.
+ leveldb::ReadOptions options;
+ options.snapshot = db_->GetSnapshot();
+
std::string map_id;
bool exists;
- if (!GetMapForArea(namespace_id, origin.spec(), &exists, &map_id))
- return;
- if (exists)
- ReadMap(map_id, result, false);
+ if (GetMapForArea(namespace_id, origin.spec(), options, &exists, &map_id) &&
+ exists)
+ ReadMap(map_id, options, result, false);
+ db_->ReleaseSnapshot(options.snapshot);
}
bool SessionStorageDatabase::CommitAreaChanges(const std::string& namespace_id,
@@ -78,7 +86,8 @@ bool SessionStorageDatabase::CommitAreaChanges(const std::string& namespace_id,
std::string map_id;
bool exists;
- if (!GetMapForArea(namespace_id, origin.spec(), &exists, &map_id))
+ if (!GetMapForArea(namespace_id, origin.spec(), leveldb::ReadOptions(),
+ &exists, &map_id))
return false;
if (exists) {
int64 ref_count;
@@ -192,7 +201,9 @@ bool SessionStorageDatabase::ReadNamespaceIds(
std::string namespace_prefix = NamespacePrefix();
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
it->Seek(namespace_prefix);
- if (it->status().IsNotFound())
+ // If the key is not found, the status of the iterator won't be IsNotFound(),
+ // but the iterator will be invalid.
+ if (!it->Valid())
return true;
if (!DatabaseErrorCheck(it->status().ok()))
@@ -223,6 +234,17 @@ bool SessionStorageDatabase::ReadNamespaceIds(
return true;
}
+bool SessionStorageDatabase::ReadOriginsInNamespace(
+ const std::string& namespace_id, std::vector<GURL>* origins) {
+ 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)
+ origins->push_back(GURL(it->first));
+ return true;
+}
+
bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
base::AutoLock auto_lock(db_lock_);
if (db_error_ || is_inconsistent_) {
@@ -337,7 +359,9 @@ bool SessionStorageDatabase::GetAreasInNamespace(
std::string namespace_start_key = NamespaceStartKey(namespace_id);
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
it->Seek(namespace_start_key);
- if (it->status().IsNotFound()) {
+ // If the key is not found, the status of the iterator won't be IsNotFound(),
+ // but the iterator will be invalid.
+ if (!it->Valid()) {
// The namespace_start_key is not found when the namespace doesn't contain
// any areas. We don't need to do anything.
return true;
@@ -373,7 +397,8 @@ bool SessionStorageDatabase::DeleteAreaHelper(
leveldb::WriteBatch* batch) {
std::string map_id;
bool exists;
- if (!GetMapForArea(namespace_id, origin, &exists, &map_id))
+ if (!GetMapForArea(namespace_id, origin, leveldb::ReadOptions(), &exists,
+ &map_id))
return false;
if (!exists)
return true; // Nothing to delete.
@@ -386,9 +411,10 @@ bool SessionStorageDatabase::DeleteAreaHelper(
bool SessionStorageDatabase::GetMapForArea(const std::string& namespace_id,
const std::string& origin,
+ const leveldb::ReadOptions& options,
bool* exists, std::string* map_id) {
std::string namespace_key = NamespaceKey(namespace_id, origin);
- leveldb::Status s = db_->Get(leveldb::ReadOptions(), namespace_key, map_id);
+ leveldb::Status s = db_->Get(options, namespace_key, map_id);
if (s.IsNotFound()) {
*exists = false;
return true;
@@ -421,13 +447,16 @@ bool SessionStorageDatabase::CreateMapForArea(const std::string& namespace_id,
}
bool SessionStorageDatabase::ReadMap(const std::string& map_id,
+ const leveldb::ReadOptions& options,
ValuesMap* result,
bool only_keys) {
- scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
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()))
+ // If the key is not found, the status of the iterator won't be IsNotFound(),
+ // but the iterator will be invalid. The map needs to exist, otherwise we have
+ // a stale map_id in the database.
+ if (!ConsistencyCheck(it->Valid()))
return false;
if (!DatabaseErrorCheck(it->status().ok()))
return false;
@@ -518,7 +547,7 @@ bool SessionStorageDatabase::DecreaseMapRefCount(const std::string& map_id,
bool SessionStorageDatabase::ClearMap(const std::string& map_id,
leveldb::WriteBatch* batch) {
ValuesMap values;
- if (!ReadMap(map_id, &values, true))
+ if (!ReadMap(map_id, leveldb::ReadOptions(), &values, true))
return false;
for (ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it)
batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first)));
@@ -549,7 +578,7 @@ bool SessionStorageDatabase::DeepCopyArea(
// Read the values from the old map here. If we don't need to copy the data,
// this can stay empty.
ValuesMap values;
- if (copy_data && !ReadMap(*map_id, &values, false))
+ if (copy_data && !ReadMap(*map_id, leveldb::ReadOptions(), &values, false))
return false;
if (!DecreaseMapRefCount(*map_id, 1, batch))
return false;
diff --git a/webkit/dom_storage/session_storage_database.h b/webkit/dom_storage/session_storage_database.h
index 51f88d7..8eab8e7 100644
--- a/webkit/dom_storage/session_storage_database.h
+++ b/webkit/dom_storage/session_storage_database.h
@@ -19,6 +19,7 @@ class GURL;
namespace leveldb {
class DB;
+struct ReadOptions;
class WriteBatch;
} // namespace leveldb
@@ -27,6 +28,9 @@ namespace dom_storage {
// SessionStorageDatabase holds the data from multiple namespaces and multiple
// origins. All DomStorageAreas for session storage share the same
// SessionStorageDatabase.
+
+// Only one thread is allowed to call the public functions other than
+// ReadAreaValues. Other threads area allowed to call ReadAreaValues.
class SessionStorageDatabase :
public base::RefCountedThreadSafe<SessionStorageDatabase> {
public:
@@ -65,6 +69,10 @@ class SessionStorageDatabase :
// Reads all namespace IDs from the database.
bool ReadNamespaceIds(std::vector<std::string>* namespace_ids);
+ // Reads all origins which have data stored in |namespace_id|.
+ bool ReadOriginsInNamespace(const std::string& namespace_id,
+ std::vector<GURL>* origins);
+
private:
friend class base::RefCountedThreadSafe<SessionStorageDatabase>;
friend class SessionStorageDatabaseTest;
@@ -125,6 +133,7 @@ class SessionStorageDatabase :
// the map doesn't exist.
bool GetMapForArea(const std::string& namespace_id,
const std::string& origin,
+ const leveldb::ReadOptions& options,
bool* exists,
std::string* map_id);
@@ -140,6 +149,7 @@ class SessionStorageDatabase :
// true, only keys are aread from the database and the values in |result| will
// be empty.
bool ReadMap(const std::string& map_id,
+ const leveldb::ReadOptions& options,
ValuesMap* result,
bool only_keys);
// Writes |values| into the map |map_id|.
diff --git a/webkit/dom_storage/session_storage_database_unittest.cc b/webkit/dom_storage/session_storage_database_unittest.cc
index de71a2f..5796b1c 100644
--- a/webkit/dom_storage/session_storage_database_unittest.cc
+++ b/webkit/dom_storage/session_storage_database_unittest.cc
@@ -52,6 +52,9 @@ class SessionStorageDatabaseTest : public testing::Test {
void CompareValuesMaps(const ValuesMap& map1, const ValuesMap& map2) const;
void CheckNamespaceIds(
const std::set<std::string>& expected_namespace_ids) const;
+ void CheckOrigins(
+ const std::string& namespace_id,
+ const std::set<GURL>& expected_origins) const;
std::string GetMapForArea(const std::string& namespace_id,
const GURL& origin) const;
int64 GetMapRefCount(const std::string& map_id) const;
@@ -335,18 +338,29 @@ void SessionStorageDatabaseTest::CheckNamespaceIds(
EXPECT_EQ(expected_namespace_ids.size(), namespace_ids.size());
for (std::vector<std::string>::const_iterator it = namespace_ids.begin();
it != namespace_ids.end(); ++it) {
- LOG(WARNING) << *it;
EXPECT_TRUE(expected_namespace_ids.find(*it) !=
expected_namespace_ids.end());
}
}
+void SessionStorageDatabaseTest::CheckOrigins(
+ const std::string& namespace_id,
+ const std::set<GURL>& expected_origins) const {
+ std::vector<GURL> origins;
+ EXPECT_TRUE(db_->ReadOriginsInNamespace(namespace_id, &origins));
+ EXPECT_EQ(expected_origins.size(), origins.size());
+ for (std::vector<GURL>::const_iterator it = origins.begin();
+ it != origins.end(); ++it) {
+ EXPECT_TRUE(expected_origins.find(*it) != expected_origins.end());
+ }
+}
+
std::string SessionStorageDatabaseTest::GetMapForArea(
const std::string& namespace_id, const GURL& origin) const {
bool exists;
std::string map_id;
EXPECT_TRUE(db_->GetMapForArea(namespace_id, origin.spec(),
- &exists, &map_id));
+ leveldb::ReadOptions(), &exists, &map_id));
EXPECT_TRUE(exists);
return map_id;
}
@@ -724,4 +738,37 @@ TEST_F(SessionStorageDatabaseTest, ReadNamespaceIds) {
CheckDatabaseConsistency();
}
+TEST_F(SessionStorageDatabaseTest, ReadNamespaceIdsInEmptyDatabase) {
+ std::set<std::string> expected_namespace_ids;
+ CheckNamespaceIds(expected_namespace_ids);
+}
+
+TEST_F(SessionStorageDatabaseTest, ReadOriginsInNamespace) {
+ ValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+
+ std::set<GURL> expected_origins1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data1));
+ expected_origins1.insert(kOrigin1);
+ expected_origins1.insert(kOrigin2);
+ CheckOrigins(kNamespace1, expected_origins1);
+
+ std::set<GURL> expected_origins2;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin2, false, data1));
+ expected_origins2.insert(kOrigin2);
+ CheckOrigins(kNamespace2, expected_origins2);
+
+ ASSERT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ CheckOrigins(kNamespaceClone, expected_origins1);
+
+ ASSERT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
+ expected_origins1.erase(kOrigin2);
+ CheckOrigins(kNamespace1, expected_origins1);
+
+ CheckDatabaseConsistency();
+}
+
} // namespace dom_storage