diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-23 01:32:45 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-23 01:32:45 +0000 |
commit | 04917467b86a41966255581c6d63a75338d8e770 (patch) | |
tree | 92a19336778b082a80bade512362f4a52d19b756 /webkit/dom_storage | |
parent | 7b6c0ccdd71595ffc25542ee5de10f0eb555fc2d (diff) | |
download | chromium_src-04917467b86a41966255581c6d63a75338d8e770.zip chromium_src-04917467b86a41966255581c6d63a75338d8e770.tar.gz chromium_src-04917467b86a41966255581c6d63a75338d8e770.tar.bz2 |
DomStorage data deletion and memory purging.
BUG=106763
Review URL: https://chromiumcodereview.appspot.com/9817011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128376 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/dom_storage')
-rw-r--r-- | webkit/dom_storage/dom_storage_area.cc | 58 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_area.h | 14 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_area_unittest.cc | 170 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_context.cc | 92 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_context.h | 4 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_database.cc | 28 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_database.h | 8 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_database_unittest.cc | 1 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_namespace.cc | 36 | ||||
-rw-r--r-- | webkit/dom_storage/dom_storage_namespace.h | 1 |
10 files changed, 367 insertions, 45 deletions
diff --git a/webkit/dom_storage/dom_storage_area.cc b/webkit/dom_storage/dom_storage_area.cc index 186a4f6..93946b8 100644 --- a/webkit/dom_storage/dom_storage_area.cc +++ b/webkit/dom_storage/dom_storage_area.cc @@ -5,14 +5,20 @@ #include "webkit/dom_storage/dom_storage_area.h" #include "base/bind.h" +#include "base/file_util.h" +#include "base/location.h" #include "base/logging.h" #include "base/time.h" -#include "base/tracked_objects.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" +#include "webkit/database/database_util.h" #include "webkit/dom_storage/dom_storage_map.h" #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/fileapi/file_system_util.h" +#include "webkit/glue/webkit_glue.h" + +using webkit_database::DatabaseUtil; namespace dom_storage { @@ -38,6 +44,14 @@ FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { InsertBeforeExtensionASCII(filename); } +// static +GURL DomStorageArea::OriginFromDatabaseFileName(const FilePath& name) { + DCHECK(name.MatchesExtension(kDatabaseFileExtension)); + WebKit::WebString origin_id = webkit_glue::FilePathToWebString( + name.BaseName().RemoveExtension()); + return DatabaseUtil::GetOriginFromIdentifier(origin_id); +} + DomStorageArea::DomStorageArea( int64 namespace_id, const GURL& origin, const FilePath& directory, DomStorageTaskRunner* task_runner) @@ -138,6 +152,48 @@ DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) { return copy; } +bool DomStorageArea::HasUncommittedChanges() const { + DCHECK(!is_shutdown_); + return commit_batch_.get() || in_flight_commit_batch_.get(); +} + +void DomStorageArea::DeleteOrigin() { + DCHECK(!is_shutdown_); + if (HasUncommittedChanges()) { + // TODO(michaeln): This logically deletes the data immediately, + // and in a matter of a second, deletes the rows from the backing + // database file, but the file itself will linger until shutdown + // or purge time. Ideally, this should delete the file more + // quickly. + Clear(); + return; + } + map_ = new DomStorageMap(kPerAreaQuota); + if (backing_.get()) { + is_initial_import_done_ = false; + backing_.reset(new DomStorageDatabase(backing_->file_path())); + file_util::Delete(backing_->file_path(), false); + file_util::Delete( + DomStorageDatabase::GetJournalFilePath(backing_->file_path()), false); + } +} + +void DomStorageArea::PurgeMemory() { + DCHECK(!is_shutdown_); + if (!is_initial_import_done_ || // We're not using any memory. + !backing_.get() || // We can't purge anything. + HasUncommittedChanges()) // We leave things alone with changes pending. + return; + + // Drop the in memory cache, we'll reload when needed. + is_initial_import_done_ = false; + map_ = new DomStorageMap(kPerAreaQuota); + + // Recreate the database object, this frees up the open sqlite connection + // and its page cache. + backing_.reset(new DomStorageDatabase(backing_->file_path())); +} + void DomStorageArea::Shutdown() { DCHECK(!is_shutdown_); is_shutdown_ = true; diff --git a/webkit/dom_storage/dom_storage_area.h b/webkit/dom_storage/dom_storage_area.h index 8d55a9b..0bad246 100644 --- a/webkit/dom_storage/dom_storage_area.h +++ b/webkit/dom_storage/dom_storage_area.h @@ -29,6 +29,7 @@ class DomStorageArea public: static const FilePath::CharType kDatabaseFileExtension[]; static FilePath DatabaseFileNameFromOrigin(const GURL& origin); + static GURL OriginFromDatabaseFileName(const FilePath& file_name); DomStorageArea(int64 namespace_id, const GURL& origin, @@ -48,6 +49,17 @@ class DomStorageArea DomStorageArea* ShallowCopy(int64 destination_namespace_id); + bool HasUncommittedChanges() const; + + // Similar to Clear() but more optimized for just deleting + // without raising events. + void DeleteOrigin(); + + // Frees up memory when possible. Typically, this method returns + // the object to its just constructed state, however if uncommitted + // changes are pending, it does nothing. + void PurgeMemory(); + // Schedules the commit of any unsaved changes and enters a // shutdown state such that the value getters and setters will // no longer do anything. @@ -60,6 +72,8 @@ class DomStorageArea FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, TestDatabaseFilePath); FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, CommitTasks); FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, CommitChangesAtShutdown); + FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, DeleteOrigin); + FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, PurgeMemory); friend class base::RefCountedThreadSafe<DomStorageArea>; struct CommitBatch { diff --git a/webkit/dom_storage/dom_storage_area_unittest.cc b/webkit/dom_storage/dom_storage_area_unittest.cc index 10c23be..12ccfd8 100644 --- a/webkit/dom_storage/dom_storage_area_unittest.cc +++ b/webkit/dom_storage/dom_storage_area_unittest.cc @@ -40,12 +40,14 @@ class DomStorageAreaTest : public testing::Test { // Verify that it put a commit in flight. EXPECT_TRUE(area->in_flight_commit_batch_.get()); EXPECT_FALSE(area->commit_batch_.get()); + EXPECT_TRUE(area->HasUncommittedChanges()); // Make additional change and verify that a new commit batch // is created for that change. NullableString16 old_value; EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_value)); EXPECT_TRUE(area->commit_batch_.get()); EXPECT_TRUE(area->in_flight_commit_batch_.get()); + EXPECT_TRUE(area->HasUncommittedChanges()); } // Class used in the CommitChangesAtShutdown test case. @@ -77,6 +79,7 @@ TEST_F(DomStorageAreaTest, DomStorageAreaBasics) { EXPECT_EQ(0u, area->Length()); EXPECT_TRUE(area->SetItem(kKey, kValue, &old_nullable_value)); EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_nullable_value)); + EXPECT_FALSE(area->HasUncommittedChanges()); // Verify that a copy shares the same map. copy = area->ShallowCopy(2); @@ -212,6 +215,7 @@ TEST_F(DomStorageAreaTest, CommitTasks) { // See that changes are batched up. EXPECT_FALSE(area->commit_batch_.get()); EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value)); + EXPECT_TRUE(area->HasUncommittedChanges()); EXPECT_TRUE(area->commit_batch_.get()); EXPECT_FALSE(area->commit_batch_->clear_all_first); EXPECT_EQ(1u, area->commit_batch_->changed_values.size()); @@ -220,6 +224,7 @@ TEST_F(DomStorageAreaTest, CommitTasks) { EXPECT_FALSE(area->commit_batch_->clear_all_first); EXPECT_EQ(2u, area->commit_batch_->changed_values.size()); MessageLoop::current()->RunAllPending(); + EXPECT_FALSE(area->HasUncommittedChanges()); EXPECT_FALSE(area->commit_batch_.get()); EXPECT_FALSE(area->in_flight_commit_batch_.get()); // Verify the changes made it to the database. @@ -245,7 +250,7 @@ TEST_F(DomStorageAreaTest, CommitTasks) { // See that if changes accrue while a commit is "in flight" // those will also get committed. EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value)); - EXPECT_TRUE(area->commit_batch_.get()); + EXPECT_TRUE(area->HasUncommittedChanges()); // At this point the OnCommitTimer task has been posted. We inject // another task in the queue that will execute after the timer task, // but before the CommitChanges task. From within our injected task, @@ -256,8 +261,7 @@ TEST_F(DomStorageAreaTest, CommitTasks) { base::Unretained(this), area)); MessageLoop::current()->RunAllPending(); EXPECT_TRUE(area->HasOneRef()); - EXPECT_FALSE(area->commit_batch_.get()); - EXPECT_FALSE(area->in_flight_commit_batch_.get()); + EXPECT_FALSE(area->HasUncommittedChanges()); // Verify the changes made it to the database. values.clear(); area->backing_->ReadAllValues(&values); @@ -281,7 +285,7 @@ TEST_F(DomStorageAreaTest, CommitChangesAtShutdown) { ValuesMap values; NullableString16 old_value; EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value)); - EXPECT_TRUE(area->commit_batch_.get()); + EXPECT_TRUE(area->HasUncommittedChanges()); area->backing_->ReadAllValues(&values); EXPECT_TRUE(values.empty()); // not committed yet area->Shutdown(); @@ -292,18 +296,156 @@ TEST_F(DomStorageAreaTest, CommitChangesAtShutdown) { // were committed. } -TEST_F(DomStorageAreaTest, TestDatabaseFilePath) { - EXPECT_EQ(FilePath().AppendASCII("file_path_to_0.localstorage"), - DomStorageArea::DatabaseFileNameFromOrigin( - GURL("file://path_to/index.html"))); +TEST_F(DomStorageAreaTest, DeleteOrigin) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + scoped_refptr<DomStorageArea> area( + new DomStorageArea(kLocalStorageNamespaceId, kOrigin, + temp_dir.path(), + new MockDomStorageTaskRunner(base::MessageLoopProxy::current()))); + + // This test puts files on disk. + FilePath db_file_path = area->backing_->file_path(); + FilePath db_journal_file_path = + DomStorageDatabase::GetJournalFilePath(db_file_path); + + // Nothing bad should happen when invoked w/o any files on disk. + area->DeleteOrigin(); + EXPECT_FALSE(file_util::PathExists(db_file_path)); + + // Commit something in the database and then delete. + NullableString16 old_value; + area->SetItem(kKey, kValue, &old_value); + MessageLoop::current()->RunAllPending(); + EXPECT_TRUE(file_util::PathExists(db_file_path)); + area->DeleteOrigin(); + EXPECT_EQ(0u, area->Length()); + EXPECT_FALSE(file_util::PathExists(db_file_path)); + EXPECT_FALSE(file_util::PathExists(db_journal_file_path)); + + // Put some uncommitted changes to a non-existing database in + // and then delete. No file ever gets created in this case. + area->SetItem(kKey, kValue, &old_value); + EXPECT_TRUE(area->HasUncommittedChanges()); + EXPECT_EQ(1u, area->Length()); + area->DeleteOrigin(); + EXPECT_TRUE(area->HasUncommittedChanges()); + EXPECT_EQ(0u, area->Length()); + MessageLoop::current()->RunAllPending(); + EXPECT_FALSE(area->HasUncommittedChanges()); + EXPECT_FALSE(file_util::PathExists(db_file_path)); - EXPECT_EQ(FilePath().AppendASCII("https_www.google.com_0.localstorage"), - DomStorageArea::DatabaseFileNameFromOrigin( - GURL("https://www.google.com/"))); + // Put some uncommitted changes to a an existing database in + // and then delete. + area->SetItem(kKey, kValue, &old_value); + MessageLoop::current()->RunAllPending(); + EXPECT_TRUE(file_util::PathExists(db_file_path)); + area->SetItem(kKey2, kValue2, &old_value); + EXPECT_TRUE(area->HasUncommittedChanges()); + EXPECT_EQ(2u, area->Length()); + area->DeleteOrigin(); + EXPECT_TRUE(area->HasUncommittedChanges()); + EXPECT_EQ(0u, area->Length()); + MessageLoop::current()->RunAllPending(); + EXPECT_FALSE(area->HasUncommittedChanges()); + // Since the area had uncommitted changes at the time delete + // was called, the file will linger until the shutdown time. + EXPECT_TRUE(file_util::PathExists(db_file_path)); + area->Shutdown(); + MessageLoop::current()->RunAllPending(); + EXPECT_FALSE(file_util::PathExists(db_file_path)); +} + +TEST_F(DomStorageAreaTest, PurgeMemory) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + scoped_refptr<DomStorageArea> area( + new DomStorageArea(kLocalStorageNamespaceId, kOrigin, + temp_dir.path(), + new MockDomStorageTaskRunner(base::MessageLoopProxy::current()))); + + // Inject an in-memory db to speed up the test. + area->backing_.reset(new DomStorageDatabase()); + + // Unowned ptrs we use to verify that 'purge' has happened. + DomStorageDatabase* original_backing = area->backing_.get(); + DomStorageMap* original_map = area->map_.get(); + + // Should do no harm when called on a newly constructed object. + EXPECT_FALSE(area->is_initial_import_done_); + area->PurgeMemory(); + EXPECT_FALSE(area->is_initial_import_done_); + EXPECT_EQ(original_backing, area->backing_.get()); + EXPECT_EQ(original_map, area->map_.get()); + + // Should not do anything when commits are pending. + NullableString16 old_value; + area->SetItem(kKey, kValue, &old_value); + EXPECT_TRUE(area->is_initial_import_done_); + EXPECT_TRUE(area->HasUncommittedChanges()); + area->PurgeMemory(); + EXPECT_TRUE(area->is_initial_import_done_); + EXPECT_TRUE(area->HasUncommittedChanges()); + EXPECT_EQ(original_backing, area->backing_.get()); + EXPECT_EQ(original_map, area->map_.get()); + + // Commit the changes from above, + MessageLoop::current()->RunAllPending(); + EXPECT_FALSE(area->HasUncommittedChanges()); + EXPECT_EQ(original_backing, area->backing_.get()); + EXPECT_EQ(original_map, area->map_.get()); + + // Should drop caches and reset database connections + // when invoked on an area that's loaded up primed. + area->PurgeMemory(); + EXPECT_FALSE(area->is_initial_import_done_); + EXPECT_NE(original_backing, area->backing_.get()); + EXPECT_NE(original_map, area->map_.get()); +} + +TEST_F(DomStorageAreaTest, DatabaseFileNames) { + struct { + const char* origin; + const char* file_name; + const char* journal_file_name; + } kCases[] = { + { "https://www.google.com/", + "https_www.google.com_0.localstorage", + "https_www.google.com_0.localstorage-journal" }, + { "http://www.google.com:8080/", + "http_www.google.com_8080.localstorage", + "http_www.google.com_8080.localstorage-journal" }, + { "file:///", + "file__0.localstorage", + "file__0.localstorage-journal" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCases); ++i) { + GURL origin = GURL(kCases[i].origin).GetOrigin(); + FilePath file_name = FilePath().AppendASCII(kCases[i].file_name); + FilePath journal_file_name = + FilePath().AppendASCII(kCases[i].journal_file_name); + + EXPECT_EQ(file_name, + DomStorageArea::DatabaseFileNameFromOrigin(origin)); + EXPECT_EQ(origin, + DomStorageArea::OriginFromDatabaseFileName(file_name)); + EXPECT_EQ(journal_file_name, + DomStorageDatabase::GetJournalFilePath(file_name)); + } - EXPECT_EQ(FilePath().AppendASCII("https_www.google.com_8080.localstorage"), - DomStorageArea::DatabaseFileNameFromOrigin( - GURL("https://www.google.com:8080"))); + // Also test some DomStorageDatabase::GetJournalFilePath cases here. + FilePath parent = FilePath().AppendASCII("a").AppendASCII("b"); + EXPECT_EQ( + parent.AppendASCII("file-journal"), + DomStorageDatabase::GetJournalFilePath(parent.AppendASCII("file"))); + EXPECT_EQ( + FilePath().AppendASCII("-journal"), + DomStorageDatabase::GetJournalFilePath(FilePath())); + EXPECT_EQ( + FilePath().AppendASCII(".extensiononly-journal"), + DomStorageDatabase::GetJournalFilePath( + FilePath().AppendASCII(".extensiononly"))); } } // namespace dom_storage diff --git a/webkit/dom_storage/dom_storage_context.cc b/webkit/dom_storage/dom_storage_context.cc index 635da33..5179a0e 100644 --- a/webkit/dom_storage/dom_storage_context.cc +++ b/webkit/dom_storage/dom_storage_context.cc @@ -7,21 +7,19 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/file_util.h" +#include "base/location.h" #include "base/time.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" #include "webkit/dom_storage/dom_storage_area.h" #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/glue/webkit_glue.h" #include "webkit/quota/special_storage_policy.h" using file_util::FileEnumerator; namespace dom_storage { -DomStorageContext::UsageInfo::UsageInfo() {} +DomStorageContext::UsageInfo::UsageInfo() : data_size(0) {} DomStorageContext::UsageInfo::~UsageInfo() {} DomStorageContext::DomStorageContext( @@ -60,35 +58,45 @@ DomStorageNamespace* DomStorageContext::GetStorageNamespace( return found->second; } -void DomStorageContext::GetUsageInfo(std::vector<UsageInfo>* infos) { +void DomStorageContext::GetUsageInfo(std::vector<UsageInfo>* infos, + bool include_file_info) { + if (directory_.empty()) + return; FileEnumerator enumerator(directory_, false, FileEnumerator::FILES); for (FilePath path = enumerator.Next(); !path.empty(); path = enumerator.Next()) { if (path.MatchesExtension(DomStorageArea::kDatabaseFileExtension)) { UsageInfo info; - - // Extract origin id from the path and construct a GURL. - WebKit::WebString origin_id = webkit_glue::FilePathToWebString( - path.BaseName().RemoveExtension()); - info.origin = GURL(WebKit::WebSecurityOrigin:: - createFromDatabaseIdentifier(origin_id).toString()); - - FileEnumerator::FindInfo find_info; - enumerator.GetFindInfo(&find_info); - info.data_size = FileEnumerator::GetFilesize(find_info); - info.last_modified = FileEnumerator::GetLastModifiedTime(find_info); - + info.origin = DomStorageArea::OriginFromDatabaseFileName(path); + if (include_file_info) { + FileEnumerator::FindInfo find_info; + enumerator.GetFindInfo(&find_info); + info.data_size = FileEnumerator::GetFilesize(find_info); + info.last_modified = FileEnumerator::GetLastModifiedTime(find_info); + } infos->push_back(info); } } } void DomStorageContext::DeleteOrigin(const GURL& origin) { - // TODO(michaeln): write me + DCHECK(!is_shutdown_); + DomStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId); + local->DeleteOrigin(origin); } void DomStorageContext::DeleteDataModifiedSince(const base::Time& cutoff) { - // TODO(michaeln): write me + std::vector<UsageInfo> infos; + const bool kIncludeFileInfo = true; + GetUsageInfo(&infos, kIncludeFileInfo); + for (size_t i = 0; i < infos.size(); ++i) { + if (infos[i].last_modified > cutoff) { + if (!special_storage_policy_ || + !special_storage_policy_->IsStorageProtected(infos[i].origin)) { + DeleteOrigin(infos[i].origin); + } + } + } } void DomStorageContext::PurgeMemory() { @@ -105,6 +113,29 @@ void DomStorageContext::Shutdown() { StorageNamespaceMap::const_iterator it = namespaces_.begin(); for (; it != namespaces_.end(); ++it) it->second->Shutdown(); + + if (directory_.empty()) + return; + + // Respect the content policy settings about what to + // keep and what to discard. + if (save_session_state_) + return; // Keep everything. + + bool has_session_only_origins = + special_storage_policy_.get() && + special_storage_policy_->HasSessionOnlyOrigins(); + + if (clear_local_state_ || has_session_only_origins) { + // We may have to delete something. We continue on the + // commit sequence after area shutdown tasks have cycled + // thru that sequence (and closed their database files). + bool success = task_runner_->PostShutdownBlockingTask( + FROM_HERE, + DomStorageTaskRunner::COMMIT_SEQUENCE, + base::Bind(&DomStorageContext::ClearLocalStateInCommitSequence, this)); + DCHECK(success); + } } void DomStorageContext::AddEventObserver(EventObserver* observer) { @@ -173,4 +204,27 @@ void DomStorageContext::CloneSessionNamespace( CreateSessionNamespace(new_id); } +void DomStorageContext::ClearLocalStateInCommitSequence() { + 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_ && + special_storage_policy_->IsStorageProtected(origin)) + continue; + if (!clear_local_state_ && + !special_storage_policy_->IsStorageSessionOnly(origin)) + continue; + + const bool kNotRecursive = false; + FilePath database_file_path = directory_.Append( + DomStorageArea::DatabaseFileNameFromOrigin(origin)); + file_util::Delete(database_file_path, kNotRecursive); + file_util::Delete( + DomStorageDatabase::GetJournalFilePath(database_file_path), + kNotRecursive); + } +} + } // namespace dom_storage diff --git a/webkit/dom_storage/dom_storage_context.h b/webkit/dom_storage/dom_storage_context.h index 2c44bc1..696f69e 100644 --- a/webkit/dom_storage/dom_storage_context.h +++ b/webkit/dom_storage/dom_storage_context.h @@ -94,7 +94,7 @@ class DomStorageContext DomStorageTaskRunner* task_runner() const { return task_runner_; } DomStorageNamespace* GetStorageNamespace(int64 namespace_id); - void GetUsageInfo(std::vector<UsageInfo>* info); + void GetUsageInfo(std::vector<UsageInfo>* infos, bool include_file_info); void DeleteOrigin(const GURL& origin); void DeleteDataModifiedSince(const base::Time& cutoff); void PurgeMemory(); @@ -152,6 +152,8 @@ class DomStorageContext ~DomStorageContext(); + void ClearLocalStateInCommitSequence(); + // Collection of namespaces keyed by id. StorageNamespaceMap namespaces_; diff --git a/webkit/dom_storage/dom_storage_database.cc b/webkit/dom_storage/dom_storage_database.cc index 93c0060..97fd3da 100644 --- a/webkit/dom_storage/dom_storage_database.cc +++ b/webkit/dom_storage/dom_storage_database.cc @@ -13,6 +13,8 @@ namespace { +const FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal"); + class HistogramUniquifier { public: static const char* name() { return "Sqlite.DomStorageDatabase.Error"; } @@ -26,14 +28,19 @@ sql::ErrorDelegate* GetErrorHandlerForDomStorageDatabase() { namespace dom_storage { +// static +FilePath DomStorageDatabase::GetJournalFilePath( + const FilePath& database_path) { + FilePath::StringType journal_file_name = + database_path.BaseName().value() + kJournal; + return database_path.DirName().Append(journal_file_name); +} + DomStorageDatabase::DomStorageDatabase(const FilePath& file_path) : file_path_(file_path) { // Note: in normal use we should never get an empty backing path here. - // However, the unit test for this class defines another constructor - // that will bypass this check to allow an empty path that signifies - // we should operate on an in-memory database for performance/reliability - // reasons. - DCHECK(!file_path_.empty()); + // However, the unit test for this class can contruct an instance + // with an empty path. Init(); } @@ -49,9 +56,10 @@ void DomStorageDatabase::Init() { DomStorageDatabase::~DomStorageDatabase() { if (known_to_be_empty_ && !file_path_.empty()) { - // Delete the db from disk, it's empty. + // Delete the db and any lingering journal file from disk. Close(); file_util::Delete(file_path_, false); + file_util::Delete(GetJournalFilePath(file_path_), false); } } @@ -74,8 +82,12 @@ void DomStorageDatabase::ReadAllValues(ValuesMap* result) { bool DomStorageDatabase::CommitChanges(bool clear_all_first, const ValuesMap& changes) { - if (!LazyOpen(!changes.empty())) - return false; + if (!LazyOpen(!changes.empty())) { + // If we're being asked to commit changes that will result in an + // empty database, we return true if the database file doesn't exist. + return clear_all_first && changes.empty() && + !file_util::PathExists(file_path_); + } bool old_known_to_be_empty = known_to_be_empty_; sql::Transaction transaction(db_.get()); diff --git a/webkit/dom_storage/dom_storage_database.h b/webkit/dom_storage/dom_storage_database.h index 6bd4dfa..5d0d9ed 100644 --- a/webkit/dom_storage/dom_storage_database.h +++ b/webkit/dom_storage/dom_storage_database.h @@ -22,6 +22,8 @@ namespace dom_storage { // class is designed to be used on a single thread. class DomStorageDatabase { public: + static FilePath GetJournalFilePath(const FilePath& database_path); + explicit DomStorageDatabase(const FilePath& file_path); virtual ~DomStorageDatabase(); // virtual for unit testing @@ -38,6 +40,9 @@ class DomStorageDatabase { // will be removed and all others will be inserted/updated as appropriate. bool CommitChanges(bool clear_all_first, const ValuesMap& changes); + // Simple getter for the path we were constructed with. + const FilePath& file_path() const { return file_path_; } + protected: // Constructor that uses an in-memory sqlite database, for testing. DomStorageDatabase(); @@ -59,6 +64,7 @@ class DomStorageDatabase { TestCanOpenFileThatIsNotADatabase); FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, BackingDatabaseOpened); FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, CommitTasks); + FRIEND_TEST_ALL_PREFIXES(DomStorageAreaTest, PurgeMemory); enum SchemaVersion { INVALID, @@ -100,7 +106,7 @@ class DomStorageDatabase { void Init(); // Path to the database on disk. - FilePath file_path_; + const FilePath file_path_; scoped_ptr<sql::Connection> db_; bool failed_to_open_; bool tried_to_recreate_; diff --git a/webkit/dom_storage/dom_storage_database_unittest.cc b/webkit/dom_storage/dom_storage_database_unittest.cc index 0757137..ddf2a69 100644 --- a/webkit/dom_storage/dom_storage_database_unittest.cc +++ b/webkit/dom_storage/dom_storage_database_unittest.cc @@ -118,6 +118,7 @@ TEST(DomStorageDatabaseTest, CloseEmptyDatabaseDeletesFile) { // trigger it's deletion from disk. { DomStorageDatabase db(file_name); + EXPECT_EQ(file_name, db.file_path()); ASSERT_TRUE(db.CommitChanges(false, storage)); } EXPECT_TRUE(file_util::PathExists(file_name)); diff --git a/webkit/dom_storage/dom_storage_namespace.cc b/webkit/dom_storage/dom_storage_namespace.cc index 7ea7f8d..656d244 100644 --- a/webkit/dom_storage/dom_storage_namespace.cc +++ b/webkit/dom_storage/dom_storage_namespace.cc @@ -64,8 +64,42 @@ DomStorageNamespace* DomStorageNamespace::Clone(int64 clone_namespace_id) { return clone; } +void DomStorageNamespace::DeleteOrigin(const GURL& origin) { + AreaHolder* holder = GetAreaHolder(origin); + if (holder) { + holder->area_->DeleteOrigin(); + return; + } + if (!directory_.empty()) { + scoped_refptr<DomStorageArea> area = + new DomStorageArea(namespace_id_, origin, directory_, task_runner_); + area->DeleteOrigin(); + } +} + void DomStorageNamespace::PurgeMemory() { - // TODO(michaeln): write me + if (directory_.empty()) + return; // We can't purge w/o backing on disk. + AreaMap::iterator it = areas_.begin(); + while (it != areas_.end()) { + // Leave it alone if changes are pending + if (it->second.area_->HasUncommittedChanges()) { + ++it; + continue; + } + + // If not in use, we can shut it down and remove + // it from our collection entirely. + if (it->second.open_count_ == 0) { + it->second.area_->Shutdown(); + areas_.erase(it++); + continue; + } + + // Otherwise, we can clear caches and such. + it->second.area_->PurgeMemory(); + ++it; + } } void DomStorageNamespace::Shutdown() { diff --git a/webkit/dom_storage/dom_storage_namespace.h b/webkit/dom_storage/dom_storage_namespace.h index a59b7b0..6063266 100644 --- a/webkit/dom_storage/dom_storage_namespace.h +++ b/webkit/dom_storage/dom_storage_namespace.h @@ -47,6 +47,7 @@ class DomStorageNamespace // Should only be called for session storage namespaces. DomStorageNamespace* Clone(int64 clone_namespace_id); + void DeleteOrigin(const GURL& origin); void PurgeMemory(); void Shutdown(); |