blob: 965068f82489cd23fe440a784f4056bdb975649c [file] [log] [blame]
// 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