blob: b050e79682e9a7f269c2951b6f494ba3cfd8adf8 [file] [log] [blame]
// Copyright (c) 2012 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/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.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_execution_context.h"
#include "content/browser/indexed_db/indexed_db_factory_impl.h"
#include "content/browser/indexed_db/indexed_db_origin_state.h"
#include "content/browser/indexed_db/leveldb/leveldb_env.h"
#include "content/browser/indexed_db/leveldb/transactional_leveldb_database.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/browser/storage_partition.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
using blink::IndexedDBDatabaseMetadata;
using url::Origin;
namespace content {
namespace {
constexpr IndexedDBExecutionContext kTestExecutionContext(4, 2);
base::FilePath CreateAndReturnTempDir(base::ScopedTempDir* temp_dir) {
CHECK(temp_dir->CreateUniqueTempDir());
return temp_dir->GetPath();
}
void CreateAndBindTransactionPlaceholder(
base::WeakPtr<IndexedDBTransaction> transaction) {}
class LevelDBLock {
public:
LevelDBLock() : env_(nullptr), lock_(nullptr) {}
LevelDBLock(leveldb::Env* env, leveldb::FileLock* lock)
: env_(env), lock_(lock) {}
~LevelDBLock() {
if (env_)
env_->UnlockFile(lock_);
}
private:
leveldb::Env* env_;
leveldb::FileLock* lock_;
DISALLOW_COPY_AND_ASSIGN(LevelDBLock);
};
std::unique_ptr<LevelDBLock> LockForTesting(const base::FilePath& file_name) {
leveldb::Env* env = LevelDBEnv::Get();
base::FilePath lock_path = file_name.AppendASCII("LOCK");
leveldb::FileLock* lock = nullptr;
leveldb::Status status = env->LockFile(lock_path.AsUTF8Unsafe(), &lock);
if (!status.ok())
return std::unique_ptr<LevelDBLock>();
DCHECK(lock);
return std::make_unique<LevelDBLock>(env, lock);
}
} // namespace
class IndexedDBTest : public testing::Test {
public:
const Origin kNormalOrigin;
const Origin kSessionOnlyOrigin;
IndexedDBTest()
: kNormalOrigin(url::Origin::Create(GURL("http://normal/"))),
kSessionOnlyOrigin(url::Origin::Create(GURL("http://session-only/"))),
special_storage_policy_(
base::MakeRefCounted<MockSpecialStoragePolicy>()),
quota_manager_proxy_(
base::MakeRefCounted<MockQuotaManagerProxy>(nullptr, nullptr)),
context_(base::MakeRefCounted<IndexedDBContextImpl>(
CreateAndReturnTempDir(&temp_dir_),
/*special_storage_policy=*/special_storage_policy_.get(),
quota_manager_proxy_.get(),
base::DefaultClock::GetInstance(),
base::SequencedTaskRunnerHandle::Get())) {
special_storage_policy_->AddSessionOnly(kSessionOnlyOrigin.GetURL());
}
~IndexedDBTest() override {
quota_manager_proxy_->SimulateQuotaManagerDestroyed();
}
void RunPostedTasks() {
base::RunLoop loop;
context_->TaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
void TearDown() override {
if (context_ && !context_->IsInMemoryContext()) {
IndexedDBFactoryImpl* factory = context_->GetIDBFactory();
// Loop through all open origins, and force close them, and request the
// deletion of the leveldb state. Once the states are no longer around,
// delete all of the databases on disk.
auto open_factory_origins = factory->GetOpenOrigins();
base::RunLoop loop;
auto callback = base::BarrierClosure(
open_factory_origins.size(), base::BindLambdaForTesting([&]() {
// All leveldb databases are closed, and they can be deleted.
for (auto origin : context_->GetAllOrigins()) {
context_->DeleteForOrigin(origin);
}
loop.Quit();
}));
for (auto origin : open_factory_origins) {
IndexedDBOriginState* per_origin_factory =
factory->GetOriginFactory(origin);
per_origin_factory->backing_store()
->db()
->leveldb_state()
->RequestDestruction(callback,
base::SequencedTaskRunnerHandle::Get());
context_->ForceClose(origin,
IndexedDBContextImpl::FORCE_CLOSE_DELETE_ORIGIN);
}
loop.Run();
}
if (temp_dir_.IsValid())
ASSERT_TRUE(temp_dir_.Delete());
}
protected:
IndexedDBContextImpl* context() const { return context_.get(); }
scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_;
private:
BrowserTaskEnvironment task_environment_;
TestBrowserContext browser_context_;
base::ScopedTempDir temp_dir_;
scoped_refptr<IndexedDBContextImpl> context_;
DISALLOW_COPY_AND_ASSIGN(IndexedDBTest);
};
TEST_F(IndexedDBTest, ClearSessionOnlyDatabases) {
base::FilePath normal_path;
base::FilePath session_only_path;
normal_path = context()->GetFilePathForTesting(kNormalOrigin);
session_only_path = context()->GetFilePathForTesting(kSessionOnlyOrigin);
ASSERT_TRUE(base::CreateDirectory(normal_path));
ASSERT_TRUE(base::CreateDirectory(session_only_path));
RunAllTasksUntilIdle();
quota_manager_proxy_->SimulateQuotaManagerDestroyed();
RunAllTasksUntilIdle();
context()->Shutdown();
RunAllTasksUntilIdle();
EXPECT_TRUE(base::DirectoryExists(normal_path));
EXPECT_FALSE(base::DirectoryExists(session_only_path));
}
TEST_F(IndexedDBTest, SetForceKeepSessionState) {
base::FilePath normal_path;
base::FilePath session_only_path;
// Save session state. This should bypass the destruction-time deletion.
context()->SetForceKeepSessionState();
normal_path = context()->GetFilePathForTesting(kNormalOrigin);
session_only_path = context()->GetFilePathForTesting(kSessionOnlyOrigin);
ASSERT_TRUE(base::CreateDirectory(normal_path));
ASSERT_TRUE(base::CreateDirectory(session_only_path));
base::RunLoop().RunUntilIdle();
context()->Shutdown();
base::RunLoop().RunUntilIdle();
// No data was cleared because of SetForceKeepSessionState.
EXPECT_TRUE(base::DirectoryExists(normal_path));
EXPECT_TRUE(base::DirectoryExists(session_only_path));
}
class ForceCloseDBCallbacks : public IndexedDBCallbacks {
public:
ForceCloseDBCallbacks(scoped_refptr<IndexedDBContextImpl> idb_context,
const Origin& origin)
: IndexedDBCallbacks(nullptr,
origin,
mojo::NullAssociatedRemote(),
idb_context->TaskRunner()),
idb_context_(idb_context),
origin_(origin) {}
void OnSuccess() override {}
void OnSuccess(const std::vector<base::string16>&) override {}
void OnSuccess(std::unique_ptr<IndexedDBConnection> connection,
const IndexedDBDatabaseMetadata& metadata) override {
connection_ = std::move(connection);
idb_context_->ConnectionOpened(origin_, connection_.get());
}
IndexedDBConnection* connection() { return connection_.get(); }
protected:
~ForceCloseDBCallbacks() override {}
private:
scoped_refptr<IndexedDBContextImpl> idb_context_;
Origin origin_;
std::unique_ptr<IndexedDBConnection> connection_;
DISALLOW_COPY_AND_ASSIGN(ForceCloseDBCallbacks);
};
TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnDelete) {
const Origin kTestOrigin = Origin::Create(GURL("http://test/"));
auto open_db_callbacks =
base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>();
auto closed_db_callbacks =
base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>();
auto open_callbacks =
base::MakeRefCounted<ForceCloseDBCallbacks>(context(), kTestOrigin);
auto closed_callbacks =
base::MakeRefCounted<ForceCloseDBCallbacks>(context(), kTestOrigin);
base::FilePath test_path = context()->GetFilePathForTesting(kTestOrigin);
const int64_t host_transaction_id = 0;
const int64_t version = 0;
IndexedDBFactory* factory = context()->GetIDBFactory();
auto create_transaction_callback1 =
base::BindOnce(&CreateAndBindTransactionPlaceholder);
factory->Open(base::ASCIIToUTF16("opendb"),
std::make_unique<IndexedDBPendingConnection>(
open_callbacks, open_db_callbacks, kTestExecutionContext,
host_transaction_id, version,
std::move(create_transaction_callback1)),
kTestOrigin, context()->data_path());
EXPECT_TRUE(base::DirectoryExists(test_path));
auto create_transaction_callback2 =
base::BindOnce(&CreateAndBindTransactionPlaceholder);
factory->Open(base::ASCIIToUTF16("closeddb"),
std::make_unique<IndexedDBPendingConnection>(
closed_callbacks, closed_db_callbacks,
kTestExecutionContext, host_transaction_id, version,
std::move(create_transaction_callback2)),
kTestOrigin, context()->data_path());
RunPostedTasks();
ASSERT_TRUE(closed_callbacks->connection());
closed_callbacks->connection()->AbortTransactionsAndClose(
IndexedDBConnection::CloseErrorHandling::kAbortAllReturnLastError);
RunPostedTasks();
context()->ForceClose(kTestOrigin,
IndexedDBContextImpl::FORCE_CLOSE_DELETE_ORIGIN);
EXPECT_TRUE(open_db_callbacks->forced_close_called());
EXPECT_FALSE(closed_db_callbacks->forced_close_called());
RunPostedTasks();
context()->DeleteForOrigin(kTestOrigin);
EXPECT_FALSE(base::DirectoryExists(test_path));
}
TEST_F(IndexedDBTest, DeleteFailsIfDirectoryLocked) {
const Origin kTestOrigin = Origin::Create(GURL("http://test/"));
base::FilePath test_path = context()->GetFilePathForTesting(kTestOrigin);
ASSERT_TRUE(base::CreateDirectory(test_path));
auto lock = LockForTesting(test_path);
ASSERT_TRUE(lock);
base::RunLoop loop;
context()->TaskRunner()->PostTask(FROM_HERE,
base::BindLambdaForTesting([&]() {
context()->DeleteForOrigin(kTestOrigin);
loop.Quit();
}));
loop.Run();
EXPECT_TRUE(base::DirectoryExists(test_path));
}
TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnCommitFailure) {
const Origin kTestOrigin = Origin::Create(GURL("http://test/"));
auto* factory =
static_cast<IndexedDBFactoryImpl*>(context()->GetIDBFactory());
const int64_t transaction_id = 1;
auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>();
auto db_callbacks = base::MakeRefCounted<MockIndexedDBDatabaseCallbacks>();
auto create_transaction_callback1 =
base::BindOnce(&CreateAndBindTransactionPlaceholder);
auto connection = std::make_unique<IndexedDBPendingConnection>(
callbacks, db_callbacks, kTestExecutionContext, transaction_id,
IndexedDBDatabaseMetadata::DEFAULT_VERSION,
std::move(create_transaction_callback1));
factory->Open(base::ASCIIToUTF16("db"), std::move(connection),
Origin(kTestOrigin), context()->data_path());
RunPostedTasks();
ASSERT_TRUE(callbacks->connection());
// ConnectionOpened() is usually called by the dispatcher.
context()->ConnectionOpened(kTestOrigin, callbacks->connection());
EXPECT_TRUE(factory->IsBackingStoreOpen(kTestOrigin));
// Simulate the write failure.
leveldb::Status status = leveldb::Status::IOError("Simulated failure");
factory->HandleBackingStoreFailure(kTestOrigin);
EXPECT_TRUE(db_callbacks->forced_close_called());
EXPECT_FALSE(factory->IsBackingStoreOpen(kTestOrigin));
}
} // namespace content