diff options
author | marja@chromium.org <marja@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-07 09:23:52 +0000 |
---|---|---|
committer | marja@chromium.org <marja@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-07 09:23:52 +0000 |
commit | 4d63bbd89567a40c8eb44098ee0bb4a9447881fc (patch) | |
tree | b4c523b2da96a50fec0874117525de9cea756d9b /webkit | |
parent | aed59fd7010f5e34598cfd9acacf01742b7f04df (diff) | |
download | chromium_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.cc | 34 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_area.h | 5 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_area_unittest.cc | 6 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_context.cc | 166 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_context.h | 28 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_namespace.cc | 23 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_namespace.h | 7 | ||||
-rw-r--r-- | webkit/dom_storage/session_storage_database.cc | 57 | ||||
-rw-r--r-- | webkit/dom_storage/session_storage_database.h | 10 | ||||
-rw-r--r-- | webkit/dom_storage/session_storage_database_unittest.cc | 51 |
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 |