| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "content/browser/dom_storage/dom_storage_area.h" |
| #include "content/browser/dom_storage/dom_storage_database_adapter.h" |
| #include "content/browser/dom_storage/dom_storage_task_runner.h" |
| #include "content/browser/dom_storage/session_storage_database.h" |
| #include "content/common/dom_storage/dom_storage_types.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| using base::ASCIIToUTF16; |
| |
| namespace content { |
| |
| class DOMStorageAreaTest : public testing::Test { |
| public: |
| DOMStorageAreaTest() |
| : kOrigin(url::Origin::Create(GURL("http://dom_storage/"))), |
| kKey(ASCIIToUTF16("key")), |
| kValue(ASCIIToUTF16("value")), |
| kKey2(ASCIIToUTF16("key2")), |
| kValue2(ASCIIToUTF16("value2")) {} |
| |
| const url::Origin kOrigin; |
| const base::string16 kKey; |
| const base::string16 kValue; |
| const base::string16 kKey2; |
| const base::string16 kValue2; |
| |
| // Method used in the CommitTasks test case. |
| void InjectedCommitSequencingTask1( |
| const scoped_refptr<DOMStorageArea>& area) { |
| // At this point the StartCommitTimer task has run and |
| // the OnCommitTimer task is queued. We want to inject after |
| // that. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DOMStorageAreaTest::InjectedCommitSequencingTask2, |
| base::Unretained(this), area)); |
| } |
| |
| void InjectedCommitSequencingTask2( |
| const scoped_refptr<DOMStorageArea>& area) { |
| // At this point the OnCommitTimer has run. |
| // Verify that it put a commit in flight. |
| EXPECT_TRUE(area->HasCommitBatchInFlight()); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| // Make additional change and verify that a new commit batch |
| // is created for that change. |
| base::NullableString16 old_value; |
| EXPECT_TRUE(area->SetItem(kKey2, kValue2, old_value, &old_value)); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()); |
| EXPECT_TRUE(area->HasCommitBatchInFlight()); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| } |
| |
| private: |
| base::test::ScopedTaskEnvironment task_environment_; |
| }; |
| |
| class DOMStorageAreaParamTest : public DOMStorageAreaTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| DOMStorageAreaParamTest() {} |
| ~DOMStorageAreaParamTest() override {} |
| }; |
| |
| INSTANTIATE_TEST_CASE_P(_, DOMStorageAreaParamTest, ::testing::Bool()); |
| |
| TEST_P(DOMStorageAreaParamTest, DOMStorageAreaBasics) { |
| const std::string kFirstNamespaceId = "id1"; |
| const std::string kSecondNamespaceId = "id2"; |
| scoped_refptr<DOMStorageArea> area( |
| new DOMStorageArea(kFirstNamespaceId, std::vector<std::string>(), kOrigin, |
| nullptr, nullptr)); |
| const bool values_cached = GetParam(); |
| area->SetCacheOnlyKeys(!values_cached); |
| base::string16 old_value; |
| base::NullableString16 old_nullable_value; |
| scoped_refptr<DOMStorageArea> copy; |
| |
| // We don't focus on the underlying DOMStorageMap functionality |
| // since that's covered by seperate unit tests. |
| EXPECT_EQ(kOrigin, area->origin()); |
| EXPECT_EQ(kFirstNamespaceId, area->namespace_id()); |
| EXPECT_EQ(0u, area->Length()); |
| EXPECT_TRUE( |
| area->SetItem(kKey, kValue, old_nullable_value, &old_nullable_value)); |
| EXPECT_TRUE( |
| area->SetItem(kKey2, kValue2, old_nullable_value, &old_nullable_value)); |
| EXPECT_FALSE(area->HasUncommittedChanges()); |
| |
| // Verify that a copy shares the same map. |
| copy = area->ShallowCopy(kSecondNamespaceId); |
| EXPECT_EQ(kOrigin, copy->origin()); |
| EXPECT_EQ(kSecondNamespaceId, copy->namespace_id()); |
| EXPECT_EQ(area->Length(), copy->Length()); |
| if (values_cached) |
| EXPECT_EQ(area->GetItem(kKey).string(), copy->GetItem(kKey).string()); |
| EXPECT_EQ(area->Key(0).string(), copy->Key(0).string()); |
| EXPECT_EQ(copy->map_.get(), area->map_.get()); |
| copy->ClearShallowCopiedCommitBatches(); |
| |
| // But will deep copy-on-write as needed. |
| old_nullable_value = base::NullableString16(kValue, false); |
| EXPECT_TRUE(area->RemoveItem(kKey, old_nullable_value, &old_value)); |
| EXPECT_EQ(kValue, old_value); |
| EXPECT_NE(copy->map_.get(), area->map_.get()); |
| copy = area->ShallowCopy(kSecondNamespaceId); |
| EXPECT_EQ(copy->map_.get(), area->map_.get()); |
| EXPECT_TRUE( |
| area->SetItem(kKey, kValue, old_nullable_value, &old_nullable_value)); |
| EXPECT_NE(copy->map_.get(), area->map_.get()); |
| copy = area->ShallowCopy(kSecondNamespaceId); |
| EXPECT_EQ(copy->map_.get(), area->map_.get()); |
| EXPECT_NE(0u, area->Length()); |
| EXPECT_TRUE(area->Clear()); |
| EXPECT_EQ(0u, area->Length()); |
| EXPECT_NE(copy->map_.get(), area->map_.get()); |
| |
| // Verify that once Shutdown(), behaves that way. |
| area->Shutdown(); |
| EXPECT_TRUE(area->is_shutdown_); |
| EXPECT_FALSE(area->map_.get()); |
| EXPECT_EQ(0u, area->Length()); |
| EXPECT_TRUE(area->Key(0).is_null()); |
| if (values_cached) |
| EXPECT_TRUE(area->GetItem(kKey).is_null()); |
| EXPECT_FALSE( |
| area->SetItem(kKey, kValue, old_nullable_value, &old_nullable_value)); |
| EXPECT_FALSE(area->RemoveItem(kKey, old_nullable_value, &old_value)); |
| EXPECT_FALSE(area->Clear()); |
| } |
| |
| TEST_F(DOMStorageAreaTest, BackingDatabaseOpened) { |
| const std::string kSessionStorageNamespaceId = "id1"; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| const base::FilePath kExpectedOriginFilePath = temp_dir.GetPath().Append( |
| DOMStorageArea::DatabaseFileNameFromOrigin(kOrigin)); |
| |
| // Valid directory and origin but no session storage backing. Backing should |
| // be null. |
| { |
| scoped_refptr<DOMStorageArea> area(new DOMStorageArea( |
| kSessionStorageNamespaceId, std::vector<std::string>(), kOrigin, |
| nullptr, nullptr)); |
| EXPECT_EQ(nullptr, area->backing_.get()); |
| |
| base::NullableString16 old_value; |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| ASSERT_TRUE(old_value.is_null()); |
| |
| // Check that saving a value has still left us without a backing database. |
| EXPECT_EQ(nullptr, area->backing_.get()); |
| EXPECT_FALSE(base::PathExists(kExpectedOriginFilePath)); |
| } |
| } |
| |
| TEST_P(DOMStorageAreaParamTest, ShallowCopyWithBacking) { |
| const std::string kFirstNamespaceId = "id1"; |
| const std::string kSecondNamespaceId = "id2"; |
| const std::string kThirdNamespaceId = "id3"; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| scoped_refptr<SessionStorageDatabase> db = new SessionStorageDatabase( |
| temp_dir.GetPath(), base::ThreadTaskRunnerHandle::Get()); |
| scoped_refptr<DOMStorageArea> area(new DOMStorageArea( |
| kFirstNamespaceId, std::vector<std::string>(), kOrigin, db.get(), |
| new MockDOMStorageTaskRunner(base::ThreadTaskRunnerHandle::Get().get()))); |
| EXPECT_TRUE(area->backing_.get()); |
| EXPECT_TRUE(area->session_storage_backing_); |
| const bool values_cached = GetParam(); |
| area->SetCacheOnlyKeys(!values_cached); |
| |
| scoped_refptr<DOMStorageArea> temp_copy; |
| temp_copy = area->ShallowCopy(kSecondNamespaceId); |
| EXPECT_TRUE(temp_copy->commit_batches_.empty()); |
| temp_copy->ClearShallowCopiedCommitBatches(); |
| |
| // Check if shallow copy is consistent. |
| base::string16 old_value; |
| base::NullableString16 old_nullable_value; |
| scoped_refptr<DOMStorageArea> copy; |
| EXPECT_TRUE( |
| area->SetItem(kKey, kValue, old_nullable_value, &old_nullable_value)); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| EXPECT_EQ(DOMStorageArea::CommitBatchHolder::TYPE_CURRENT_BATCH, |
| area->commit_batches_.front().type); |
| copy = area->ShallowCopy(kThirdNamespaceId); |
| EXPECT_EQ(copy->map_.get(), area->map_.get()); |
| EXPECT_EQ(1u, copy->original_namespace_ids_.size()); |
| EXPECT_EQ(kFirstNamespaceId, copy->original_namespace_ids_[0]); |
| if (!values_cached) { |
| EXPECT_EQ(area->commit_batches_.front().batch, |
| copy->commit_batches_.front().batch); |
| EXPECT_EQ(DOMStorageArea::CommitBatchHolder::TYPE_IN_FLIGHT, |
| area->commit_batches_.front().type); |
| EXPECT_EQ(DOMStorageArea::CommitBatchHolder::TYPE_CLONE, |
| copy->commit_batches_.front().type); |
| } else { |
| EXPECT_TRUE(copy->commit_batches_.empty()); |
| } |
| |
| DOMStorageValuesMap map; |
| copy->ExtractValues(&map); |
| EXPECT_EQ(1u, map.size()); |
| EXPECT_EQ(kValue, map[kKey].string()); |
| |
| // Check if copy on write works. |
| EXPECT_TRUE( |
| copy->SetItem(kKey2, kValue2, old_nullable_value, &old_nullable_value)); |
| EXPECT_TRUE(copy->GetCurrentCommitBatch()); |
| EXPECT_FALSE(copy->commit_batches_.front().type); |
| if (!values_cached) |
| EXPECT_EQ(DOMStorageArea::CommitBatchHolder::TYPE_CLONE, |
| copy->commit_batches_.back().type); |
| else |
| EXPECT_FALSE(copy->HasCommitBatchInFlight()); |
| EXPECT_EQ(1u, area->Length()); |
| |
| // Check clearing of cloned batches. |
| area->ClearShallowCopiedCommitBatches(); |
| copy->ClearShallowCopiedCommitBatches(); |
| EXPECT_EQ(DOMStorageArea::CommitBatchHolder::TYPE_IN_FLIGHT, |
| area->commit_batches_.front().type); |
| EXPECT_FALSE(copy->HasCommitBatchInFlight()); |
| } |
| |
| TEST_F(DOMStorageAreaTest, SetCacheOnlyKeysWithoutBacking) { |
| const std::string kFirstNamespaceId = "id1"; |
| scoped_refptr<DOMStorageArea> area( |
| new DOMStorageArea(kFirstNamespaceId, std::vector<std::string>(), kOrigin, |
| nullptr, nullptr)); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_AND_VALUES, |
| area->desired_load_state_); |
| EXPECT_FALSE(area->map_->has_only_keys()); |
| base::NullableString16 old_value; |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| DOMStorageValuesMap map; |
| area->ExtractValues(&map); |
| EXPECT_EQ(1u, map.size()); |
| |
| area->SetCacheOnlyKeys(true); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_AND_VALUES, |
| area->desired_load_state_); // cannot be disabled without backing. |
| area->SetItem(kKey, kValue2, old_value, &old_value); |
| EXPECT_FALSE(area->map_->has_only_keys()); |
| EXPECT_EQ(kValue, old_value.string()); |
| area->ExtractValues(&map); |
| EXPECT_EQ(kValue2, map[kKey].string()); |
| EXPECT_EQ(1u, map.size()); |
| } |
| |
| TEST_F(DOMStorageAreaTest, SetCacheOnlyKeysWithBacking) { |
| const std::string kNamespaceId = "id1"; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| scoped_refptr<SessionStorageDatabase> db = new SessionStorageDatabase( |
| temp_dir.GetPath(), base::ThreadTaskRunnerHandle::Get()); |
| scoped_refptr<DOMStorageArea> area(new DOMStorageArea( |
| kNamespaceId, std::vector<std::string>(), kOrigin, db.get(), |
| new MockDOMStorageTaskRunner(base::ThreadTaskRunnerHandle::Get().get()))); |
| |
| EXPECT_TRUE(area->backing_.get()); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_UNLOADED, area->load_state_); |
| |
| #if !defined(OS_ANDROID) |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_AND_VALUES, |
| area->desired_load_state_); |
| area->SetCacheOnlyKeys(true); |
| #endif |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_ONLY, area->desired_load_state_); |
| base::NullableString16 old_value; |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_ONLY, area->load_state_); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()); |
| EXPECT_EQ(1u, area->Length()); |
| |
| // Fill the current batch and in flight batch. |
| EXPECT_TRUE(area->SetItem(kKey2, kValue, old_value, &old_value)); |
| area->PostCommitTask(); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()); |
| EXPECT_TRUE(area->HasCommitBatchInFlight()); |
| EXPECT_TRUE(area->SetItem(kKey2, kValue2, old_value, &old_value)); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()); |
| |
| // The values must be imported from the backing, and from the commit batches. |
| area->SetCacheOnlyKeys(false); |
| EXPECT_EQ(2u, area->Length()); |
| EXPECT_EQ(kValue, area->GetItem(kKey).string()); |
| EXPECT_EQ(kValue2, area->GetItem(kKey2).string()); |
| |
| // Check if disabling cache clears the cache after committing only. |
| area->SetCacheOnlyKeys(true); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_ONLY, area->desired_load_state_); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_AND_VALUES, area->load_state_); |
| ASSERT_FALSE(area->map_->has_only_keys()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_ONLY, area->load_state_); |
| EXPECT_TRUE(area->map_->has_only_keys()); |
| EXPECT_FALSE(area->HasCommitBatchInFlight()); |
| |
| // Check if Clear() works as expected when values are desired. |
| area->Clear(); |
| EXPECT_TRUE(area->SetItem(kKey2, kValue2, old_value, &old_value)); |
| area->PostCommitTask(); |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| EXPECT_EQ(2u, area->Length()); |
| area->Clear(); |
| EXPECT_TRUE(area->SetItem(kKey2, kValue, old_value, &old_value)); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()->batch->clear_all_first); |
| EXPECT_TRUE(area->commit_batches_.back().batch->clear_all_first); |
| area->SetCacheOnlyKeys(false); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_ONLY, area->load_state_); |
| |
| // Unload only after commit. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_UNLOADED, area->load_state_); |
| EXPECT_EQ(1u, area->Length()); |
| EXPECT_EQ(kValue, area->GetItem(kKey2).string()); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_KEYS_AND_VALUES, area->load_state_); |
| } |
| |
| TEST_P(DOMStorageAreaParamTest, CommitTasks) { |
| const std::string kNamespaceId = "id1"; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| scoped_refptr<SessionStorageDatabase> db = new SessionStorageDatabase( |
| temp_dir.GetPath(), base::ThreadTaskRunnerHandle::Get()); |
| scoped_refptr<DOMStorageArea> area(new DOMStorageArea( |
| kNamespaceId, std::vector<std::string>(), kOrigin, db.get(), |
| new MockDOMStorageTaskRunner(base::ThreadTaskRunnerHandle::Get().get()))); |
| |
| area->SetCacheOnlyKeys(GetParam()); |
| |
| // Unrelated to commits, but while we're here, see that querying Length() |
| // causes the backing database to be opened and presumably read from. |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_UNLOADED, area->load_state_); |
| EXPECT_EQ(0u, area->Length()); |
| EXPECT_EQ(area->desired_load_state_, area->load_state_); |
| |
| DOMStorageValuesMap values; |
| base::NullableString16 old_value; |
| |
| // See that changes are batched up. |
| EXPECT_FALSE(area->GetCurrentCommitBatch()); |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()->batch->clear_all_first); |
| EXPECT_EQ(1u, area->GetCurrentCommitBatch()->batch->changed_values.size()); |
| EXPECT_TRUE(area->SetItem(kKey2, kValue2, old_value, &old_value)); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()->batch->clear_all_first); |
| EXPECT_EQ(2u, area->GetCurrentCommitBatch()->batch->changed_values.size()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(area->HasUncommittedChanges()); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()); |
| EXPECT_FALSE(area->HasCommitBatchInFlight()); |
| // Verify the changes made it to the database. |
| values.clear(); |
| area->backing_->ReadAllValues(&values); |
| EXPECT_EQ(2u, values.size()); |
| EXPECT_EQ(kValue, values[kKey].string()); |
| EXPECT_EQ(kValue2, values[kKey2].string()); |
| |
| // See that clear is handled properly. |
| EXPECT_TRUE(area->Clear()); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()->batch->clear_all_first); |
| EXPECT_TRUE(area->GetCurrentCommitBatch()->batch->changed_values.empty()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(area->GetCurrentCommitBatch()); |
| EXPECT_FALSE(area->HasCommitBatchInFlight()); |
| // Verify the changes made it to the database. |
| values.clear(); |
| area->backing_->ReadAllValues(&values); |
| EXPECT_TRUE(values.empty()); |
| |
| // See that if changes accrue while a commit is "in flight" |
| // those will also get committed. |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| // At this point the StartCommitTimer task has been posted to the after |
| // startup task queue. We inject another task in the queue that will |
| // execute when the CommitChanges task is inflight. From within our |
| // injected task, we'll make an additional SetItem() call and verify |
| // that a new commit batch is created for that additional change. |
| BrowserThread::PostAfterStartupTask( |
| FROM_HERE, base::ThreadTaskRunnerHandle::Get(), |
| base::BindOnce(&DOMStorageAreaTest::InjectedCommitSequencingTask1, |
| base::Unretained(this), area)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(area->HasOneRef()); |
| EXPECT_FALSE(area->HasUncommittedChanges()); |
| // Verify the changes made it to the database. |
| values.clear(); |
| area->backing_->ReadAllValues(&values); |
| EXPECT_EQ(2u, values.size()); |
| EXPECT_EQ(kValue, values[kKey].string()); |
| EXPECT_EQ(kValue2, values[kKey2].string()); |
| } |
| |
| TEST_P(DOMStorageAreaParamTest, CommitChangesAtShutdown) { |
| const std::string kNamespaceId = "id1"; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| scoped_refptr<SessionStorageDatabase> db = new SessionStorageDatabase( |
| temp_dir.GetPath(), base::ThreadTaskRunnerHandle::Get()); |
| scoped_refptr<DOMStorageArea> area(new DOMStorageArea( |
| kNamespaceId, std::vector<std::string>(), kOrigin, db.get(), |
| new MockDOMStorageTaskRunner(base::ThreadTaskRunnerHandle::Get().get()))); |
| |
| area->SetCacheOnlyKeys(GetParam()); |
| |
| DOMStorageValuesMap values; |
| base::NullableString16 old_value; |
| EXPECT_TRUE(area->SetItem(kKey, kValue, old_value, &old_value)); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| area->backing_->ReadAllValues(&values); |
| EXPECT_TRUE(values.empty()); // not committed yet |
| area->Shutdown(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(area->HasOneRef()); |
| EXPECT_FALSE(area->backing_.get()); |
| |
| // Verify changes were committed. |
| db->ReadAreaValues(kNamespaceId, std::vector<std::string>(), kOrigin, |
| &values); |
| EXPECT_EQ(1u, values.size()); |
| EXPECT_EQ(kValue, values[kKey].string()); |
| |
| // A second Shutdown call should be safe. |
| area->Shutdown(); |
| } |
| |
| TEST_P(DOMStorageAreaParamTest, PurgeMemory) { |
| const std::string kNamespaceId = "id1"; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| scoped_refptr<SessionStorageDatabase> db = new SessionStorageDatabase( |
| temp_dir.GetPath(), base::ThreadTaskRunnerHandle::Get()); |
| scoped_refptr<DOMStorageArea> area(new DOMStorageArea( |
| kNamespaceId, std::vector<std::string>(), kOrigin, db.get(), |
| new MockDOMStorageTaskRunner(base::ThreadTaskRunnerHandle::Get().get()))); |
| area->SetCacheOnlyKeys(GetParam()); |
| |
| // Unowned ptr we use to verify that 'purge' has happened. |
| DOMStorageMap* original_map = area->map_.get(); |
| |
| // Should do no harm when called on a newly constructed object. |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_UNLOADED, area->load_state_); |
| area->PurgeMemory(); |
| EXPECT_EQ(DOMStorageArea::LOAD_STATE_UNLOADED, area->load_state_); |
| EXPECT_EQ(original_map, area->map_.get()); |
| |
| // Should not do anything when commits are pending. |
| base::NullableString16 old_value; |
| area->SetItem(kKey, kValue, old_value, &old_value); |
| original_map = area->map_.get(); // importing creates new map. |
| EXPECT_EQ(area->desired_load_state_, area->load_state_); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| area->PurgeMemory(); |
| EXPECT_EQ(area->desired_load_state_, area->load_state_); |
| EXPECT_TRUE(area->HasUncommittedChanges()); |
| EXPECT_EQ(original_map, area->map_.get()); |
| |
| // Commit the changes from above, |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(area->HasUncommittedChanges()); |
| 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_EQ(DOMStorageArea::LOAD_STATE_UNLOADED, area->load_state_); |
| EXPECT_NE(original_map, area->map_.get()); |
| } |
| |
| TEST_F(DOMStorageAreaTest, DatabaseFileNames) { |
| struct { |
| const char* origin; |
| const char* file_name; |
| } kCases[] = { |
| {"https://www.google.com/", "https_www.google.com_0.localstorage"}, |
| {"http://www.google.com:8080/", "http_www.google.com_8080.localstorage"}, |
| {"file:///", "file__0.localstorage"}, |
| }; |
| |
| for (size_t i = 0; i < base::size(kCases); ++i) { |
| url::Origin origin = |
| url::Origin::Create(GURL(kCases[i].origin).GetOrigin()); |
| base::FilePath file_name = |
| base::FilePath().AppendASCII(kCases[i].file_name); |
| |
| EXPECT_EQ(file_name, |
| DOMStorageArea::DatabaseFileNameFromOrigin(origin)); |
| EXPECT_EQ(origin, |
| DOMStorageArea::OriginFromDatabaseFileName(file_name)); |
| } |
| } |
| |
| TEST_F(DOMStorageAreaTest, RateLimiter) { |
| // Limit to 1000 samples per second |
| DOMStorageArea::RateLimiter rate_limiter( |
| 1000, base::TimeDelta::FromSeconds(1)); |
| |
| // No samples have been added so no time/delay should be needed. |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeTimeNeeded()); |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeDelayNeeded(base::TimeDelta())); |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeDelayNeeded(base::TimeDelta::FromDays(1))); |
| |
| // Add a seconds worth of samples. |
| rate_limiter.add_samples(1000); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(1), |
| rate_limiter.ComputeTimeNeeded()); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(1), |
| rate_limiter.ComputeDelayNeeded(base::TimeDelta())); |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeDelayNeeded(base::TimeDelta::FromSeconds(1))); |
| EXPECT_EQ(base::TimeDelta::FromMilliseconds(250), |
| rate_limiter.ComputeDelayNeeded( |
| base::TimeDelta::FromMilliseconds(750))); |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeDelayNeeded( |
| base::TimeDelta::FromDays(1))); |
| |
| // And another half seconds worth. |
| rate_limiter.add_samples(500); |
| EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500), |
| rate_limiter.ComputeTimeNeeded()); |
| EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500), |
| rate_limiter.ComputeDelayNeeded(base::TimeDelta())); |
| EXPECT_EQ(base::TimeDelta::FromMilliseconds(500), |
| rate_limiter.ComputeDelayNeeded(base::TimeDelta::FromSeconds(1))); |
| EXPECT_EQ(base::TimeDelta::FromMilliseconds(750), |
| rate_limiter.ComputeDelayNeeded( |
| base::TimeDelta::FromMilliseconds(750))); |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeDelayNeeded( |
| base::TimeDelta::FromMilliseconds(1500))); |
| EXPECT_EQ(base::TimeDelta(), |
| rate_limiter.ComputeDelayNeeded( |
| base::TimeDelta::FromDays(1))); |
| } |
| |
| } // namespace content |