| // 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 <stdint.h> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/time/default_clock.h" |
| #include "content/browser/indexed_db/indexed_db_connection.h" |
| #include "content/browser/indexed_db/indexed_db_context_impl.h" |
| #include "content/browser/indexed_db/indexed_db_data_format_version.h" |
| #include "content/browser/indexed_db/indexed_db_factory_impl.h" |
| #include "content/browser/indexed_db/leveldb/leveldb_env.h" |
| #include "content/browser/indexed_db/mock_indexed_db_callbacks.h" |
| #include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/public/test/test_utils.h" |
| #include "storage/browser/test/mock_quota_manager_proxy.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/indexeddb/web_idb_types.h" |
| #include "third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| using base::ASCIIToUTF16; |
| using blink::IndexedDBDatabaseMetadata; |
| using url::Origin; |
| |
| namespace content { |
| |
| namespace { |
| |
| base::FilePath CreateAndReturnTempDir(base::ScopedTempDir* temp_dir) { |
| CHECK(temp_dir->CreateUniqueTempDir()); |
| return temp_dir->GetPath(); |
| } |
| |
| void CreateAndBindTransactionPlaceholder( |
| base::WeakPtr<IndexedDBTransaction> transaction) {} |
| |
| class MockIDBFactory : public IndexedDBFactoryImpl { |
| public: |
| explicit MockIDBFactory(IndexedDBContextImpl* context) |
| : MockIDBFactory(context, base::DefaultClock::GetInstance()) {} |
| MockIDBFactory(IndexedDBContextImpl* context, base::Clock* clock) |
| : IndexedDBFactoryImpl(context, |
| indexed_db::GetDefaultLevelDBFactory(), |
| clock) {} |
| scoped_refptr<IndexedDBBackingStore> TestOpenBackingStore( |
| const Origin& origin, |
| const base::FilePath& data_directory) { |
| IndexedDBDataLossInfo data_loss_info; |
| bool disk_full; |
| leveldb::Status s; |
| scoped_refptr<IndexedDBBackingStore> backing_store; |
| std::tie(backing_store, s, data_loss_info, disk_full) = |
| OpenBackingStore(origin, data_directory); |
| EXPECT_EQ(blink::mojom::IDBDataLoss::None, data_loss_info.status); |
| return backing_store; |
| } |
| |
| void TestCloseBackingStore(IndexedDBBackingStore* backing_store) { |
| CloseBackingStore(backing_store->origin()); |
| } |
| |
| void TestReleaseBackingStore(IndexedDBBackingStore* backing_store, |
| bool immediate) { |
| ReleaseBackingStore(backing_store->origin(), immediate); |
| } |
| |
| private: |
| ~MockIDBFactory() override {} |
| |
| DISALLOW_COPY_AND_ASSIGN(MockIDBFactory); |
| }; |
| |
| } // namespace |
| |
| class IndexedDBFactoryTest : public testing::Test { |
| public: |
| IndexedDBFactoryTest() |
| : quota_manager_proxy_( |
| base::MakeRefCounted<MockQuotaManagerProxy>(nullptr, nullptr)), |
| context_(base::MakeRefCounted<IndexedDBContextImpl>( |
| CreateAndReturnTempDir(&temp_dir_), |
| /*special_storage_policy=*/nullptr, |
| quota_manager_proxy_.get(), |
| indexed_db::GetDefaultLevelDBFactory(), |
| base::DefaultClock::GetInstance())) {} |
| |
| void TearDown() override { |
| quota_manager_proxy_->SimulateQuotaManagerDestroyed(); |
| } |
| |
| protected: |
| IndexedDBContextImpl* context() const { return context_.get(); } |
| |
| private: |
| TestBrowserThreadBundle thread_bundle_; |
| |
| base::ScopedTempDir temp_dir_; |
| scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; |
| scoped_refptr<IndexedDBContextImpl> context_; |
| |
| DISALLOW_COPY_AND_ASSIGN(IndexedDBFactoryTest); |
| }; |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreLifetime) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| |
| const Origin origin1 = Origin::Create(GURL("http://localhost:81")); |
| const Origin origin2 = Origin::Create(GURL("http://localhost:82")); |
| |
| auto disk_store1 = |
| factory->TestOpenBackingStore(origin1, context()->data_path()); |
| |
| auto disk_store2 = |
| factory->TestOpenBackingStore(origin1, context()->data_path()); |
| EXPECT_EQ(disk_store1.get(), disk_store2.get()); |
| |
| auto disk_store3 = |
| factory->TestOpenBackingStore(origin2, context()->data_path()); |
| |
| factory->TestCloseBackingStore(disk_store1.get()); |
| factory->TestCloseBackingStore(disk_store3.get()); |
| |
| EXPECT_FALSE(disk_store1->HasOneRef()); |
| EXPECT_FALSE(disk_store2->HasOneRef()); |
| EXPECT_TRUE(disk_store3->HasOneRef()); |
| |
| disk_store2 = nullptr; |
| EXPECT_TRUE(disk_store1->HasOneRef()); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreLazyClose) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| |
| auto store = |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| |
| // Give up the local refptr so that the factory has the only |
| // outstanding reference. |
| IndexedDBBackingStore* store_ptr = store.get(); |
| store = nullptr; |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| |
| // Take back a ref ptr and ensure that the actual close |
| // stops a running timer. |
| store = store_ptr; |
| factory->TestCloseBackingStore(store_ptr); |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreNoSweeping) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| {}, {kIDBTombstoneDeletion, kIDBTombstoneStatistics}); |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| base::SimpleTestClock clock; |
| clock.SetNow(base::Time::Now()); |
| |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context(), &clock); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| |
| auto store = |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| |
| // Give up the local refptr so that the factory has the only |
| // outstanding reference. |
| IndexedDBBackingStore* store_ptr = store.get(); |
| store = nullptr; |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| EXPECT_EQ(nullptr, store_ptr->pre_close_task_queue()); |
| |
| // Reset the timer & stop the closing. |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| store_ptr->close_timer()->FireNow(); |
| |
| // Backing store should be totally closed. |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| |
| store = factory->TestOpenBackingStore(origin, context()->data_path()); |
| store_ptr = store.get(); |
| store = nullptr; |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| |
| // Move the clock to start the next sweep. |
| clock.Advance(IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| |
| // Sweep should NOT be occurring. |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| store_ptr->close_timer()->FireNow(); |
| |
| // Backing store should be totally closed. |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreRunPreCloseTasks) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures({kIDBTombstoneStatistics}, |
| {kIDBTombstoneDeletion}); |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| base::SimpleTestClock clock; |
| clock.SetNow(base::Time::Now()); |
| |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context(), &clock); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| |
| auto store = |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| |
| // Give up the local refptr so that the factory has the only |
| // outstanding reference. |
| IndexedDBBackingStore* store_ptr = store.get(); |
| store = nullptr; |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| EXPECT_EQ(nullptr, store_ptr->pre_close_task_queue()); |
| |
| // Reset the timer & stop the closing. |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| store_ptr->close_timer()->FireNow(); |
| |
| // Backing store should be totally closed. |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| |
| store = factory->TestOpenBackingStore(origin, context()->data_path()); |
| store_ptr = store.get(); |
| store = nullptr; |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| |
| // Move the clock to start the next sweep. |
| clock.Advance(IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| |
| // Sweep should be occuring. |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| store_ptr->close_timer()->FireNow(); |
| ASSERT_NE(nullptr, store_ptr->pre_close_task_queue()); |
| EXPECT_TRUE(store_ptr->pre_close_task_queue()->started()); |
| |
| // Stop sweep by opening a connection. |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| EXPECT_EQ(nullptr, store_ptr->pre_close_task_queue()); |
| |
| // Move clock forward to trigger next sweep, but origin has longer |
| // sweep minimum, so nothing happens. |
| clock.Advance(IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow); |
| |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| EXPECT_EQ(nullptr, store_ptr->pre_close_task_queue()); |
| |
| // Reset, and move clock forward so the origin should allow a sweep. |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| EXPECT_EQ(nullptr, store_ptr->pre_close_task_queue()); |
| clock.Advance(IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| |
| // Sweep should be occuring. |
| EXPECT_TRUE(store_ptr->close_timer()->IsRunning()); |
| store_ptr->close_timer()->FireNow(); |
| ASSERT_NE(nullptr, store_ptr->pre_close_task_queue()); |
| EXPECT_TRUE(store_ptr->pre_close_task_queue()->started()); |
| |
| // Take back a ref ptr and ensure that the actual close |
| // stops a running timer. |
| store = store_ptr; |
| factory->TestCloseBackingStore(store_ptr); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreCloseImmediatelySwitch) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures({kIDBTombstoneStatistics}, |
| {kIDBTombstoneDeletion}); |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| kIDBCloseImmediatelySwitch); |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| base::SimpleTestClock clock; |
| clock.SetNow(base::Time::Now()); |
| |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context(), &clock); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| |
| auto store = |
| factory->TestOpenBackingStore(origin, context()->data_path()); |
| |
| // Give up the local refptr so that the factory has the only |
| // outstanding reference. |
| IndexedDBBackingStore* store_ptr = store.get(); |
| store = nullptr; |
| EXPECT_FALSE(store_ptr->close_timer()->IsRunning()); |
| factory->TestReleaseBackingStore(store_ptr, false); |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, MemoryBackingStoreLifetime) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| |
| const Origin origin1 = Origin::Create(GURL("http://localhost:81")); |
| const Origin origin2 = Origin::Create(GURL("http://localhost:82")); |
| |
| auto mem_store1 = |
| factory->TestOpenBackingStore(origin1, base::FilePath()); |
| |
| auto mem_store2 = |
| factory->TestOpenBackingStore(origin1, base::FilePath()); |
| EXPECT_EQ(mem_store1.get(), mem_store2.get()); |
| |
| auto mem_store3 = |
| factory->TestOpenBackingStore(origin2, base::FilePath()); |
| |
| factory->TestCloseBackingStore(mem_store1.get()); |
| factory->TestCloseBackingStore(mem_store3.get()); |
| |
| EXPECT_FALSE(mem_store1->HasOneRef()); |
| EXPECT_FALSE(mem_store2->HasOneRef()); |
| EXPECT_FALSE(mem_store3->HasOneRef()); |
| |
| factory = nullptr; |
| EXPECT_FALSE(mem_store1->HasOneRef()); // mem_store1 and 2 |
| EXPECT_FALSE(mem_store2->HasOneRef()); // mem_store1 and 2 |
| EXPECT_TRUE(mem_store3->HasOneRef()); |
| |
| mem_store2 = nullptr; |
| EXPECT_TRUE(mem_store1->HasOneRef()); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, MemoryBackingStoreDetectedAsIncognito) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| const Origin origin1 = Origin::Create(GURL("http://localhost:81")); |
| auto mem_store1 = |
| factory->TestOpenBackingStore(origin1, base::FilePath()); |
| EXPECT_TRUE(mem_store1->is_incognito()); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, RejectLongOrigins) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| base::FilePath temp_dir = context()->data_path().DirName(); |
| int limit = base::GetMaximumPathComponentLength(temp_dir); |
| EXPECT_GT(limit, 0); |
| |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| |
| std::string origin(limit + 1, 'x'); |
| Origin too_long_origin = |
| Origin::Create(GURL("http://" + origin + ":81/")); |
| auto diskStore1 = factory->TestOpenBackingStore(too_long_origin, |
| context()->data_path()); |
| EXPECT_FALSE(diskStore1.get()); |
| |
| Origin ok_origin = Origin::Create(GURL("http://someorigin.com:82/")); |
| auto diskStore2 = |
| factory->TestOpenBackingStore(ok_origin, context()->data_path()); |
| EXPECT_TRUE(diskStore2.get()); |
| // We need a manual close or Windows can't delete the temp |
| // directory. |
| factory->TestCloseBackingStore(diskStore2.get()); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| } |
| |
| class DiskFullFactory : public IndexedDBFactoryImpl { |
| public: |
| explicit DiskFullFactory(IndexedDBContextImpl* context) |
| : IndexedDBFactoryImpl(context, |
| indexed_db::GetDefaultLevelDBFactory(), |
| base::DefaultClock::GetInstance()) {} |
| |
| private: |
| ~DiskFullFactory() override {} |
| |
| std::tuple<scoped_refptr<IndexedDBBackingStore>, |
| leveldb::Status, |
| IndexedDBDataLossInfo, |
| bool /* disk_full */> |
| OpenBackingStore(const url::Origin& origin, |
| const base::FilePath& data_directory) override { |
| return std::make_tuple(nullptr, leveldb::Status::IOError("Disk is full"), |
| IndexedDBDataLossInfo(), true); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(DiskFullFactory); |
| }; |
| |
| class LookingForQuotaErrorMockCallbacks : public IndexedDBCallbacks { |
| public: |
| LookingForQuotaErrorMockCallbacks() |
| : IndexedDBCallbacks(nullptr, |
| url::Origin(), |
| nullptr, |
| base::SequencedTaskRunnerHandle::Get()), |
| error_called_(false) {} |
| void OnError(const IndexedDBDatabaseError& error) override { |
| error_called_ = true; |
| EXPECT_EQ(blink::kWebIDBDatabaseExceptionQuotaError, error.code()); |
| } |
| bool error_called() const { return error_called_; } |
| |
| private: |
| ~LookingForQuotaErrorMockCallbacks() override {} |
| bool error_called_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LookingForQuotaErrorMockCallbacks); |
| }; |
| |
| TEST_F(IndexedDBFactoryTest, QuotaErrorOnDiskFull) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto callbacks = |
| base::MakeRefCounted<LookingForQuotaErrorMockCallbacks>(); |
| auto dummy_database_callbacks = |
| base::MakeRefCounted<IndexedDBDatabaseCallbacks>( |
| nullptr, nullptr, context()->TaskRunner()); |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| auto factory = base::MakeRefCounted<DiskFullFactory>(context()); |
| const base::string16 name(ASCIIToUTF16("name")); |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| callbacks, dummy_database_callbacks, /*child_process_id=*/0, |
| /*transaction_id=*/2, /*version=*/1, |
| std::move(create_transaction_callback)); |
| factory->Open(name, std::move(connection), origin, |
| context()->data_path()); |
| EXPECT_TRUE(callbacks->error_called()); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreReleasedOnForcedClose) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>(); |
| auto db_callbacks = |
| base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>(); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| const int64_t transaction_id = 1; |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| callbacks, db_callbacks, /*child_process_id=*/0, transaction_id, |
| IndexedDBDatabaseMetadata::DEFAULT_VERSION, |
| std::move(create_transaction_callback)); |
| factory->Open(ASCIIToUTF16("db"), std::move(connection), origin, |
| context()->data_path()); |
| |
| EXPECT_TRUE(callbacks->connection()); |
| |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| |
| callbacks->connection()->ForceClose(); |
| |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, BackingStoreReleaseDelayedOnClose) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>(); |
| auto db_callbacks = |
| base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>(); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| const int64_t transaction_id = 1; |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| callbacks, db_callbacks, /*child_process_id=*/0, transaction_id, |
| IndexedDBDatabaseMetadata::DEFAULT_VERSION, |
| std::move(create_transaction_callback)); |
| factory->Open(ASCIIToUTF16("db"), std::move(connection), origin, |
| context()->data_path()); |
| |
| EXPECT_TRUE(callbacks->connection()); |
| IndexedDBBackingStore* store = |
| callbacks->connection()->database()->backing_store(); |
| EXPECT_FALSE(store->HasOneRef()); // Factory and database. |
| |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| callbacks->connection()->Close(); |
| EXPECT_TRUE(store->HasOneRef()); // Factory. |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_TRUE(factory->IsBackingStorePendingClose(origin)); |
| EXPECT_TRUE(store->close_timer()->IsRunning()); |
| |
| // Take a ref so it won't be destroyed out from under the test. |
| scoped_refptr<IndexedDBBackingStore> store_ref = store; |
| |
| // Now simulate shutdown, which should stop the timer. |
| factory->ContextDestroyed(); |
| EXPECT_TRUE(store->HasOneRef()); // Local. |
| EXPECT_FALSE(store->close_timer()->IsRunning()); |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, DeleteDatabaseClosesBackingStore) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>( |
| /*expect_connection=*/false); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| |
| factory->DeleteDatabase(ASCIIToUTF16("db"), callbacks, origin, |
| context()->data_path(), |
| /*force_close=*/false); |
| |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_TRUE(factory->IsBackingStorePendingClose(origin)); |
| |
| // Now simulate shutdown, which should stop the timer. |
| factory->ContextDestroyed(); |
| |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, GetDatabaseNamesClosesBackingStore) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>( |
| /*expect_connection=*/false); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| |
| factory->GetDatabaseNames(callbacks, origin, context()->data_path()); |
| |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_TRUE(factory->IsBackingStorePendingClose(origin)); |
| |
| // Now simulate shutdown, which should stop the timer. |
| factory->ContextDestroyed(); |
| |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(IndexedDBFactoryTest, ForceCloseReleasesBackingStore) { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>(); |
| auto db_callbacks = |
| base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>(); |
| |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| const int64_t transaction_id = 1; |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| callbacks, db_callbacks, /*child_process_id=*/0, transaction_id, |
| IndexedDBDatabaseMetadata::DEFAULT_VERSION, |
| std::move(create_transaction_callback)); |
| factory->Open(ASCIIToUTF16("db"), std::move(connection), origin, |
| context()->data_path()); |
| |
| EXPECT_TRUE(callbacks->connection()); |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| |
| callbacks->connection()->Close(); |
| |
| EXPECT_TRUE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_TRUE(factory->IsBackingStorePendingClose(origin)); |
| |
| factory->ForceClose(origin, /*delete_in_memory_store=*/false); |
| |
| EXPECT_FALSE(factory->IsBackingStoreOpen(origin)); |
| EXPECT_FALSE(factory->IsBackingStorePendingClose(origin)); |
| |
| // Ensure it is safe if the store is not open. |
| factory->ForceClose(origin, /*delete_in_memory_store=*/false); |
| |
| loop.Quit(); |
| })), |
| loop.Run(); |
| } |
| |
| class UpgradeNeededCallbacks : public MockIndexedDBCallbacks { |
| public: |
| UpgradeNeededCallbacks() {} |
| |
| void OnSuccess(std::unique_ptr<IndexedDBConnection> connection, |
| const IndexedDBDatabaseMetadata& metadata) override { |
| EXPECT_TRUE(connection_.get()); |
| EXPECT_FALSE(connection.get()); |
| } |
| |
| void OnUpgradeNeeded(int64_t old_version, |
| std::unique_ptr<IndexedDBConnection> connection, |
| const IndexedDBDatabaseMetadata& metadata, |
| const IndexedDBDataLossInfo& data_loss_info) override { |
| connection_ = std::move(connection); |
| } |
| |
| protected: |
| ~UpgradeNeededCallbacks() override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(UpgradeNeededCallbacks); |
| }; |
| |
| class ErrorCallbacks : public MockIndexedDBCallbacks { |
| public: |
| ErrorCallbacks() : MockIndexedDBCallbacks(false), saw_error_(false) {} |
| |
| void OnError(const IndexedDBDatabaseError& error) override { |
| saw_error_ = true; |
| } |
| bool saw_error() const { return saw_error_; } |
| |
| private: |
| ~ErrorCallbacks() override {} |
| bool saw_error_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ErrorCallbacks); |
| }; |
| |
| // Disabled due to flakiness: see https://crbug.com/947667. |
| TEST_F(IndexedDBFactoryTest, DISABLED_DatabaseFailedOpen) { |
| const Origin origin = Origin::Create(GURL("http://localhost:81")); |
| const base::string16 db_name(ASCIIToUTF16("db")); |
| const int64_t transaction_id = 1; |
| |
| // These objects are retained across posted tasks, so despite being used |
| // exclusively on the IDB sequence. |
| |
| // Created and used on IDB sequence. |
| scoped_refptr<MockIDBFactory> factory; |
| scoped_refptr<UpgradeNeededCallbacks> upgrade_callbacks; |
| scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks; |
| scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks2; |
| scoped_refptr<ErrorCallbacks> failed_open_callbacks; |
| |
| { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| upgrade_callbacks = base::MakeRefCounted<UpgradeNeededCallbacks>(); |
| db_callbacks = base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>(); |
| failed_open_callbacks = base::MakeRefCounted<ErrorCallbacks>(); |
| db_callbacks2 = |
| base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>(); |
| // Open at version 2. |
| const int64_t db_version = 2; |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| upgrade_callbacks, db_callbacks, |
| /*child_process_id=*/0, transaction_id, db_version, |
| std::move(create_transaction_callback)); |
| factory->Open(db_name, std::move(connection), origin, |
| context()->data_path()); |
| EXPECT_TRUE(factory->IsDatabaseOpen(origin, db_name)); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| // Close the connection. |
| { |
| EXPECT_TRUE(upgrade_callbacks->connection()); |
| upgrade_callbacks->connection()->database()->Commit( |
| upgrade_callbacks->connection()->GetTransaction( |
| transaction_id)); |
| upgrade_callbacks->connection()->Close(); |
| EXPECT_FALSE(factory->IsDatabaseOpen(origin, db_name)); |
| } |
| |
| // Open at version < 2, which will fail; ensure factory doesn't |
| // retain the database object. |
| { |
| const int64_t db_version = 1; |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| failed_open_callbacks, db_callbacks2, |
| /*child_process_id=*/0, transaction_id, db_version, |
| std::move(create_transaction_callback)); |
| factory->Open(db_name, std::move(connection), origin, |
| context()->data_path()); |
| EXPECT_TRUE(failed_open_callbacks->saw_error()); |
| EXPECT_FALSE(factory->IsDatabaseOpen(origin, db_name)); |
| } |
| |
| // Terminate all pending-close timers. |
| factory->ForceClose(origin, /*delete_in_memory_store=*/false); |
| loop.Quit(); |
| |
| // These need to be deleted on the IDB task runner. |
| upgrade_callbacks.reset(); |
| db_callbacks.reset(); |
| db_callbacks2.reset(); |
| failed_open_callbacks.reset(); |
| })); |
| loop.Run(); |
| } |
| } |
| |
| namespace { |
| |
| class DataLossCallbacks final : public MockIndexedDBCallbacks { |
| public: |
| blink::mojom::IDBDataLoss data_loss() const { return data_loss_; } |
| void OnSuccess(std::unique_ptr<IndexedDBConnection> connection, |
| const IndexedDBDatabaseMetadata& metadata) override { |
| if (!connection_) |
| connection_ = std::move(connection); |
| } |
| void OnError(const IndexedDBDatabaseError& error) final { |
| ADD_FAILURE() << "Unexpected IDB error: " << error.message(); |
| } |
| void OnUpgradeNeeded(int64_t old_version, |
| std::unique_ptr<IndexedDBConnection> connection, |
| const IndexedDBDatabaseMetadata& metadata, |
| const IndexedDBDataLossInfo& data_loss) final { |
| connection_ = std::move(connection); |
| data_loss_ = data_loss.status; |
| } |
| |
| private: |
| ~DataLossCallbacks() final {} |
| blink::mojom::IDBDataLoss data_loss_ = blink::mojom::IDBDataLoss::None; |
| }; |
| |
| TEST_F(IndexedDBFactoryTest, DataFormatVersion) { |
| auto try_open = [this](const Origin& origin, |
| const IndexedDBDataFormatVersion& version) { |
| base::AutoReset<IndexedDBDataFormatVersion> override_version( |
| &IndexedDBDataFormatVersion::GetMutableCurrentForTesting(), version); |
| |
| // These objects are retained across posted tasks, so despite being used |
| // exclusively on the IDB sequence. |
| |
| // Created and used on IDB sequence. |
| scoped_refptr<MockIDBFactory> factory; |
| scoped_refptr<DataLossCallbacks> callbacks; |
| |
| const int64_t transaction_id = 1; |
| blink::mojom::IDBDataLoss result; |
| |
| { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| factory = base::MakeRefCounted<MockIDBFactory>(context()); |
| callbacks = base::MakeRefCounted<DataLossCallbacks>(); |
| auto db_callbacks = |
| base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>(); |
| auto create_transaction_callback = |
| base::BindOnce(&CreateAndBindTransactionPlaceholder); |
| auto connection = std::make_unique<IndexedDBPendingConnection>( |
| callbacks, db_callbacks, /*child_process_id=*/0, transaction_id, |
| /*version=*/1, std::move(create_transaction_callback)); |
| factory->Open(ASCIIToUTF16("test_db"), std::move(connection), |
| origin, context()->data_path()); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| auto* connection = callbacks->connection(); |
| EXPECT_TRUE(connection); |
| connection->database()->Commit( |
| connection->GetTransaction(transaction_id)); |
| connection->Close(); |
| factory->ForceClose(origin, /*delete_in_memory_store=*/false); |
| result = callbacks->data_loss(); |
| loop.Quit(); |
| callbacks.reset(); |
| })); |
| loop.Run(); |
| } |
| { |
| base::RunLoop loop; |
| context()->TaskRunner()->PostTask(FROM_HERE, |
| base::BindLambdaForTesting([&]() { |
| callbacks.reset(); |
| factory.reset(); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| return result; |
| }; |
| |
| static const struct { |
| const char* origin; |
| IndexedDBDataFormatVersion open_version_1; |
| IndexedDBDataFormatVersion open_version_2; |
| blink::mojom::IDBDataLoss expected_data_loss; |
| } kTestCases[] = { |
| {"http://same-version.com/", |
| {3, 4}, |
| {3, 4}, |
| blink::mojom::IDBDataLoss::None}, |
| {"http://blink-upgrade.com/", |
| {3, 4}, |
| {3, 5}, |
| blink::mojom::IDBDataLoss::None}, |
| {"http://v8-upgrade.com/", |
| {3, 4}, |
| {4, 4}, |
| blink::mojom::IDBDataLoss::None}, |
| {"http://both-upgrade.com/", |
| {3, 4}, |
| {4, 5}, |
| blink::mojom::IDBDataLoss::None}, |
| {"http://blink-downgrade.com/", |
| {3, 4}, |
| {3, 3}, |
| blink::mojom::IDBDataLoss::Total}, |
| {"http://v8-downgrade.com/", |
| {3, 4}, |
| {2, 4}, |
| blink::mojom::IDBDataLoss::Total}, |
| {"http://both-downgrade.com/", |
| {3, 4}, |
| {2, 3}, |
| blink::mojom::IDBDataLoss::Total}, |
| {"http://v8-up-blink-down.com/", |
| {3, 4}, |
| {4, 2}, |
| blink::mojom::IDBDataLoss::Total}, |
| {"http://v8-down-blink-up.com/", |
| {3, 4}, |
| {2, 5}, |
| blink::mojom::IDBDataLoss::Total}, |
| }; |
| for (const auto& test : kTestCases) { |
| SCOPED_TRACE(test.origin); |
| const Origin origin = Origin::Create(GURL(test.origin)); |
| ASSERT_EQ(blink::mojom::IDBDataLoss::None, |
| try_open(origin, test.open_version_1)); |
| EXPECT_EQ(test.expected_data_loss, try_open(origin, test.open_version_2)); |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace content |