blob: 90754c5254486be6a5f3eecad7a5d37f90695cc9 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <inttypes.h>
#include <map>
#include <memory>
#include <optional>
#include <ostream>
#include <set>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/clamped_math.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_id.h"
#include "components/services/storage/privileged/mojom/indexed_db_control.mojom.h"
#include "components/services/storage/public/cpp/buckets/bucket_id.h"
#include "components/services/storage/public/cpp/buckets/bucket_info.h"
#include "components/services/storage/public/cpp/buckets/bucket_init_params.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "components/services/storage/public/cpp/buckets/constants.h"
#include "components/services/storage/public/cpp/quota_error_or.h"
#include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
#include "content/browser/indexed_db/indexed_db_backing_store.h"
#include "content/browser/indexed_db/indexed_db_bucket_context.h"
#include "content/browser/indexed_db/indexed_db_bucket_context_handle.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_leveldb_coding.h"
#include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h"
#include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h"
#include "content/browser/indexed_db/mock_mojo_indexed_db_factory_client.h"
#include "content/public/common/content_features.h"
#include "env_chromium.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "net/base/features.h"
#include "net/base/schemeful_site.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
#include "third_party/blink/public/mojom/storage_key/ancestor_chain_bit.mojom-shared.h"
#include "url/gurl.h"
#include "url/origin.h"
using base::test::RunClosure;
using blink::IndexedDBDatabaseMetadata;
using blink::IndexedDBIndexKeys;
using blink::IndexedDBKey;
using storage::BucketLocator;
using testing::_;
using testing::StrictMock;
using url::Origin;
namespace content {
namespace {
constexpr char16_t kDatabaseName[] = u"db";
constexpr char kOrigin[] = "https://www.example.com";
// TODO(crbug.com/41417435): Replace with common converter.
url::Origin ToOrigin(const std::string& url) {
return url::Origin::Create(GURL(url));
}
MATCHER_P(IsAssociatedInterfacePtrInfoValid,
tf,
std::string(negation ? "isn't" : "is") + " " +
std::string(tf ? "valid" : "invalid")) {
return tf == arg->is_valid();
}
ACTION_P(QuitLoop, run_loop) {
run_loop->Quit();
}
ACTION_TEMPLATE(MoveArgPointee,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_1_VALUE_PARAMS(out)) {
*out = std::move(*::testing::get<k>(args));
}
base::FilePath CreateAndReturnTempDir(base::ScopedTempDir* temp_dir) {
CHECK(temp_dir->CreateUniqueTempDir());
return temp_dir->GetPath();
}
storage::BucketInfo ToBucketInfo(const BucketLocator& bucket_locator) {
storage::BucketInfo bucket_info;
bucket_info.id = bucket_locator.id;
bucket_info.storage_key = bucket_locator.storage_key;
bucket_info.name = storage::kDefaultBucketName;
return bucket_info;
}
// Stores data specific to a connection.
struct TestDatabaseConnection {
TestDatabaseConnection() = default;
TestDatabaseConnection(scoped_refptr<base::SequencedTaskRunner> task_runner,
url::Origin origin,
std::u16string db_name,
int64_t version,
int64_t upgrade_txn_id)
: task_runner(std::move(task_runner)),
origin(std::move(origin)),
db_name(std::move(db_name)),
version(version),
upgrade_txn_id(upgrade_txn_id),
open_callbacks(
std::make_unique<StrictMock<MockMojoIndexedDBFactoryClient>>()),
connection_callbacks(
std::make_unique<
StrictMock<MockMojoIndexedDBDatabaseCallbacks>>()) {}
TestDatabaseConnection(const TestDatabaseConnection&) = delete;
TestDatabaseConnection& operator=(const TestDatabaseConnection&) = delete;
TestDatabaseConnection(TestDatabaseConnection&&) noexcept = default;
TestDatabaseConnection& operator=(TestDatabaseConnection&&) noexcept =
default;
~TestDatabaseConnection() = default;
void Open(blink::mojom::IDBFactory* factory) {
factory->Open(
open_callbacks->CreateInterfacePtrAndBind(),
connection_callbacks->CreateInterfacePtrAndBind(), db_name, version,
version_change_transaction.BindNewEndpointAndPassReceiver(task_runner),
upgrade_txn_id);
// ForcedClose is called on shutdown and depending on ordering and timing
// may or may not happen, which is fine.
EXPECT_CALL(*connection_callbacks, ForcedClose())
.Times(testing::AnyNumber());
}
scoped_refptr<base::SequencedTaskRunner> task_runner;
url::Origin origin;
std::u16string db_name;
int64_t version;
int64_t upgrade_txn_id;
mojo::AssociatedRemote<blink::mojom::IDBDatabase> database;
mojo::AssociatedRemote<blink::mojom::IDBTransaction>
version_change_transaction;
std::unique_ptr<MockMojoIndexedDBFactoryClient> open_callbacks;
std::unique_ptr<MockMojoIndexedDBDatabaseCallbacks> connection_callbacks;
};
class TestIndexedDBObserver : public storage::mojom::IndexedDBObserver {
public:
explicit TestIndexedDBObserver(
mojo::PendingReceiver<storage::mojom::IndexedDBObserver> receiver)
: receiver_(this, std::move(receiver)) {}
void OnIndexedDBListChanged(const BucketLocator& bucket_locator) override {
++notify_list_changed_count;
}
void OnIndexedDBContentChanged(
const BucketLocator& bucket_locator,
const std::u16string& database_name,
const std::u16string& object_store_name) override {
++notify_content_changed_count;
}
int notify_list_changed_count = 0;
int notify_content_changed_count = 0;
private:
mojo::Receiver<storage::mojom::IndexedDBObserver> receiver_;
};
} // namespace
class IndexedDBTest
: public testing::Test,
// The first boolean toggles the Storage Partitioning feature. The second
// boolean controls the type of StorageKey to run the test on (first or
// third party). The third boolean controls whether to enable backing
// store sharding.
public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
public:
blink::StorageKey kNormalFirstPartyStorageKey;
BucketLocator kNormalFirstPartyBucketLocator;
blink::StorageKey kSessionOnlyFirstPartyStorageKey;
BucketLocator kSessionOnlyFirstPartyBucketLocator;
blink::StorageKey kSessionOnlySubdomainFirstPartyStorageKey;
BucketLocator kSessionOnlySubdomainFirstPartyBucketLocator;
blink::StorageKey kNormalThirdPartyStorageKey;
BucketLocator kNormalThirdPartyBucketLocator;
blink::StorageKey kSessionOnlyThirdPartyStorageKey;
BucketLocator kSessionOnlyThirdPartyBucketLocator;
blink::StorageKey kSessionOnlySubdomainThirdPartyStorageKey;
BucketLocator kSessionOnlySubdomainThirdPartyBucketLocator;
blink::StorageKey kInvertedNormalThirdPartyStorageKey;
BucketLocator kInvertedNormalThirdPartyBucketLocator;
blink::StorageKey kInvertedSessionOnlyThirdPartyStorageKey;
BucketLocator kInvertedSessionOnlyThirdPartyBucketLocator;
blink::StorageKey kInvertedSessionOnlySubdomainThirdPartyStorageKey;
BucketLocator kInvertedSessionOnlySubdomainThirdPartyBucketLocator;
IndexedDBTest()
: special_storage_policy_(
base::MakeRefCounted<storage::MockSpecialStoragePolicy>()),
quota_manager_(base::MakeRefCounted<storage::MockQuotaManager>(
/*is_incognito=*/false,
CreateAndReturnTempDir(&temp_dir_),
base::SingleThreadTaskRunner::GetCurrentDefault(),
special_storage_policy_)),
quota_manager_proxy_(
base::MakeRefCounted<storage::MockQuotaManagerProxy>(
quota_manager_.get(),
base::SequencedTaskRunner::GetCurrentDefault())),
context_(std::make_unique<IndexedDBContextImpl>(
temp_dir_.GetPath(),
quota_manager_proxy_.get(),
/*blob_storage_context=*/mojo::NullRemote(),
/*file_system_access_context=*/mojo::NullRemote(),
base::SequencedTaskRunner::GetCurrentDefault(),
base::SequencedTaskRunner::GetCurrentDefault())) {
scoped_feature_list_.InitWithFeatureStates(
{{net::features::kThirdPartyStoragePartitioning,
IsThirdPartyStoragePartitioningEnabled()},
{features::kIndexedDBShardBackingStores, BackingStoresSharded()}});
kNormalFirstPartyStorageKey =
blink::StorageKey::CreateFromStringForTesting("http://normal.com/");
storage::BucketInfo bucket_info = InitBucket(kNormalFirstPartyStorageKey);
kNormalFirstPartyBucketLocator = bucket_info.ToBucketLocator();
kSessionOnlyFirstPartyStorageKey =
blink::StorageKey::CreateFromStringForTesting(
"http://session-only.com/");
bucket_info = InitBucket(kSessionOnlyFirstPartyStorageKey);
kSessionOnlyFirstPartyBucketLocator = bucket_info.ToBucketLocator();
kSessionOnlySubdomainFirstPartyStorageKey =
blink::StorageKey::CreateFromStringForTesting(
"http://subdomain.session-only.com/");
bucket_info = InitBucket(kSessionOnlySubdomainFirstPartyStorageKey);
kSessionOnlySubdomainFirstPartyBucketLocator =
bucket_info.ToBucketLocator();
kNormalThirdPartyStorageKey = blink::StorageKey::Create(
url::Origin::Create(GURL("http://normal.com/")),
net::SchemefulSite(GURL("http://rando.com/")),
blink::mojom::AncestorChainBit::kCrossSite);
bucket_info = InitBucket(kNormalThirdPartyStorageKey);
kNormalThirdPartyBucketLocator = bucket_info.ToBucketLocator();
kSessionOnlyThirdPartyStorageKey = blink::StorageKey::Create(
url::Origin::Create(GURL("http://session-only.com/")),
net::SchemefulSite(GURL("http://rando.com/")),
blink::mojom::AncestorChainBit::kCrossSite);
bucket_info = InitBucket(kSessionOnlyThirdPartyStorageKey);
kSessionOnlyThirdPartyBucketLocator = bucket_info.ToBucketLocator();
kSessionOnlySubdomainThirdPartyStorageKey = blink::StorageKey::Create(
url::Origin::Create(GURL("http://subdomain.session-only.com/")),
net::SchemefulSite(GURL("http://rando.com/")),
blink::mojom::AncestorChainBit::kCrossSite);
bucket_info = InitBucket(kSessionOnlySubdomainThirdPartyStorageKey);
kSessionOnlySubdomainThirdPartyBucketLocator =
bucket_info.ToBucketLocator();
kInvertedNormalThirdPartyStorageKey = blink::StorageKey::Create(
url::Origin::Create(GURL("http://rando.com/")),
net::SchemefulSite(GURL("http://normal.com/")),
blink::mojom::AncestorChainBit::kCrossSite);
bucket_info = InitBucket(kInvertedNormalThirdPartyStorageKey);
kInvertedNormalThirdPartyBucketLocator = bucket_info.ToBucketLocator();
kInvertedSessionOnlyThirdPartyStorageKey = blink::StorageKey::Create(
url::Origin::Create(GURL("http://rando.com/")),
net::SchemefulSite(GURL("http://session-only.com/")),
blink::mojom::AncestorChainBit::kCrossSite);
bucket_info = InitBucket(kInvertedSessionOnlyThirdPartyStorageKey);
kInvertedSessionOnlyThirdPartyBucketLocator = bucket_info.ToBucketLocator();
kInvertedSessionOnlySubdomainThirdPartyStorageKey =
blink::StorageKey::Create(
url::Origin::Create(GURL("http://rando.com/")),
net::SchemefulSite(GURL("http://subdomain.session-only.com/")),
blink::mojom::AncestorChainBit::kCrossSite);
bucket_info = InitBucket(kInvertedSessionOnlySubdomainThirdPartyStorageKey);
kInvertedSessionOnlySubdomainThirdPartyBucketLocator =
bucket_info.ToBucketLocator();
std::vector<storage::mojom::StoragePolicyUpdatePtr> policy_updates;
policy_updates.emplace_back(storage::mojom::StoragePolicyUpdate::New(
url::Origin::Create(GURL("http://subdomain.session-only.com")),
/*should_purge_on_shutdown=*/true));
context_->ApplyPolicyUpdates(std::move(policy_updates));
}
IndexedDBTest(const IndexedDBTest&) = delete;
IndexedDBTest& operator=(const IndexedDBTest&) = delete;
~IndexedDBTest() override = default;
storage::BucketInfo InitBucket(const blink::StorageKey& storage_key) {
storage::BucketInfo bucket;
quota_manager_->UpdateOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(storage_key),
base::BindOnce(
[](storage::BucketInfo* info,
storage::QuotaErrorOr<storage::BucketInfo> bucket_info) {
*info = bucket_info.value();
},
&bucket));
return bucket;
}
void SetUpInMemoryContext() {
context_ = std::make_unique<IndexedDBContextImpl>(
base::FilePath(), quota_manager_proxy_.get(),
/*blob_storage_context=*/mojo::NullRemote(),
/*file_system_access_context=*/mojo::NullRemote(),
base::SequencedTaskRunner::GetCurrentDefault(),
base::SequencedTaskRunner::GetCurrentDefault());
}
void RunPostedTasks() {
base::RunLoop loop;
context_->IDBTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
void TearDown() override {
factory_remote_.reset();
if (context_ && !context_->in_memory()) {
std::set<BucketLocator> buckets = context_->bucket_set_;
for (const BucketLocator& bucket_locator : buckets) {
context_->DeleteBucketData(bucket_locator, base::DoNothing());
}
while (!context_->GetOpenBucketIdsForTesting().empty()) {
RunPostedTasks();
}
}
if (temp_dir_.IsValid()) {
ASSERT_TRUE(temp_dir_.Delete());
}
}
base::FilePath GetFilePathForTesting(const BucketLocator& bucket_locator) {
base::test::TestFuture<const base::FilePath&> path_future;
context()->GetFilePathForTesting(bucket_locator, path_future.GetCallback());
return path_future.Take();
}
bool IsThirdPartyStoragePartitioningEnabled() {
return std::get<0>(GetParam());
}
bool BackingStoresSharded() { return std::get<2>(GetParam()); }
bool DeleteBucket(const storage::BucketInfo* bucket_info) {
base::test::TestFuture<blink::mojom::QuotaStatusCode> result_code;
context()->DeleteBucketData(bucket_info->ToBucketLocator(),
result_code.GetCallback());
return result_code.Get() == blink::mojom::QuotaStatusCode::kOk;
}
void BindIndexedDBFactory(
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote,
mojo::PendingReceiver<blink::mojom::IDBFactory> receiver,
storage::QuotaErrorOr<storage::BucketInfo> bucket_info) {
context()->BindIndexedDBImpl(std::move(checker_remote),
base::UnguessableToken(), std::move(receiver),
bucket_info);
}
blink::StorageKey GetTestStorageKey() {
const bool first_party = std::get<1>(GetParam());
return first_party
? blink::StorageKey::CreateFromStringForTesting("http://test/")
: blink::StorageKey::Create(
url::Origin::Create(GURL("http://test/")),
net::SchemefulSite(GURL("http://rando/")),
blink::mojom::AncestorChainBit::kCrossSite);
}
// Opens a database connection, runs `action`, and verifies that the
// connection was forced closed.
void VerifyForcedClosedCalled(base::OnceClosure action,
storage::BucketInfo* out_info = nullptr) {
storage::BucketInfo bucket_info = InitBucket(GetTestStorageKey());
if (out_info) {
*out_info = bucket_info;
}
BucketLocator bucket_locator = bucket_info.ToBucketLocator();
base::FilePath test_path = GetFilePathForTesting(bucket_locator);
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote_.BindNewPipeAndPassReceiver(),
bucket_info);
// Open new connection/database, wait for success.
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
// It's necessary to hang onto the database connection or the connection
// will shut itself down and there will be no `ForcedClosed()`.
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
EXPECT_CALL(client, MockedOpenSuccess)
.WillOnce(
testing::DoAll(MoveArgPointee<0>(&pending_database),
::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote_->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(),
u"opendb", /*version=*/0,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*host_transaction_id=*/0);
run_loop.Run();
EXPECT_TRUE(base::DirectoryExists(test_path));
// Expect that deleting the data force closes the open database connection.
base::RunLoop run_loop2;
EXPECT_CALL(database_callbacks, ForcedClose())
.WillOnce(::base::test::RunClosure(run_loop2.QuitClosure()));
std::move(action).Run();
run_loop2.Run();
}
IndexedDBBucketContext& GetOrCreateBucketContext(
const storage::BucketInfo& bucket,
const base::FilePath& data_directory) {
context_->EnsureBucketContext(bucket, data_directory);
return *GetBucketContext(bucket.id);
}
// This will assert if `ForceSingleThreadForTesting()` has not been called.
IndexedDBBucketContext* GetBucketContext(storage::BucketId id) {
if (BackingStoresSharded()) {
auto* sequence_bound = context_->GetShardedBucketContextForTesting(id);
if (!sequence_bound) {
return nullptr;
}
base::test::TestFuture<IndexedDBBucketContext*> future;
sequence_bound->AsyncCall(&IndexedDBBucketContext::GetReferenceForTesting)
.Then(future.GetCallback());
return future.Get();
}
return context_->GetBucketContextForTesting(id);
}
storage::BucketInfo GetOrCreateBucket(
const storage::BucketInitParams& params) {
base::test::TestFuture<storage::QuotaErrorOr<storage::BucketInfo>> future;
quota_manager_proxy_->UpdateOrCreateBucket(
params, base::SingleThreadTaskRunner::GetCurrentDefault(),
future.GetCallback());
return future.Take().value();
}
IndexedDBBucketContextHandle CreateBucketHandle(
std::optional<storage::BucketLocator> bucket_locator = std::nullopt) {
context_->ForceSingleThreadForTesting();
if (!bucket_locator) {
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
bucket_locator = BucketLocator();
bucket_locator->storage_key = storage_key;
}
context_->EnsureBucketContext(ToBucketInfo(*bucket_locator),
context()->GetDataPath(*bucket_locator));
IndexedDBBucketContextHandle bucket_context_handle(
*GetBucketContext(bucket_locator->id));
bucket_context_handle->InitBackingStoreIfNeeded(
/*create_if_missing=*/true);
return bucket_context_handle;
}
void VerifyBucketContextWaitIfNeeded(const storage::BucketId& id,
bool expected_context_exists) {
while (expected_context_exists != context_->BucketContextExists(id)) {
RunPostedTasks();
}
VerifyBucketContext(id, expected_context_exists);
}
void VerifyBucketContext(
const storage::BucketId& id,
bool expected_context_exists,
std::optional<bool> expected_backing_store_exists = std::nullopt) {
IndexedDBBucketContext* context = GetBucketContext(id);
if (!expected_context_exists) {
EXPECT_FALSE(context);
EXPECT_FALSE(expected_backing_store_exists.has_value());
} else {
ASSERT_TRUE(context);
if (expected_backing_store_exists.has_value()) {
EXPECT_EQ(*expected_backing_store_exists, !!context->backing_store());
}
}
}
protected:
IndexedDBContextImpl* context() const { return context_.get(); }
base::test::ScopedFeatureList scoped_feature_list_;
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
base::ScopedTempDir temp_dir_;
scoped_refptr<storage::MockSpecialStoragePolicy> special_storage_policy_;
scoped_refptr<storage::MockQuotaManager> quota_manager_;
scoped_refptr<storage::MockQuotaManagerProxy> quota_manager_proxy_;
std::unique_ptr<IndexedDBContextImpl> context_;
mojo::Remote<blink::mojom::IDBFactory> factory_remote_;
};
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
IndexedDBTest,
testing::Combine(
/*enable third party storage partitioning*/ testing::Bool(),
testing::Values(true),
/*enable backing store sharding*/ testing::Bool()),
[](const testing::TestParamInfo<IndexedDBTest::ParamType>& info) {
std::string name = std::get<0>(info.param) ? "WithStoragePartitioning_"
: "NoStoragePartitioning_";
name += std::get<2>(info.param) ? "WithBackingStoredSharding"
: "NoBackingStoreSharding";
return name;
});
TEST_P(IndexedDBTest, CloseConnectionBeforeUpgrade) {
const int64_t kDBVersion = 1;
const int64_t kTransactionId = 1;
std::unique_ptr<TestDatabaseConnection> connection;
IndexedDBDatabaseMetadata metadata;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
base::RunLoop loop;
connection = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), url::Origin::Create(GURL(kOrigin)),
kDatabaseName, kDBVersion, kTransactionId);
EXPECT_CALL(
*connection->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(""), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database),
testing::SaveArg<4>(&metadata),
QuitLoop(&loop)));
connection->Open(bounded_factory_remote.get());
loop.Run();
EXPECT_TRUE(pending_database.is_valid());
EXPECT_EQ(connection->version, metadata.version);
EXPECT_EQ(connection->db_name, metadata.name);
// Close the connection to finish the test nicely.
connection.reset();
}
TEST_P(IndexedDBTest, CloseAfterUpgrade) {
const int64_t kDBVersion = 1;
const int64_t kTransactionId = 1;
const int64_t kObjectStoreId = 10;
const char16_t kObjectStoreName[] = u"os";
std::unique_ptr<TestDatabaseConnection> connection;
IndexedDBDatabaseMetadata metadata;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
base::RunLoop loop;
// Open connection.
connection = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
kTransactionId);
EXPECT_CALL(
*connection->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(""), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database),
testing::SaveArg<4>(&metadata),
QuitLoop(&loop)));
// Queue open request message.
connection->Open(bounded_factory_remote.get());
loop.Run();
ASSERT_TRUE(pending_database.is_valid());
EXPECT_EQ(connection->version, metadata.version);
EXPECT_EQ(connection->db_name, metadata.name);
base::RunLoop loop2;
base::RepeatingClosure quit_closure2 =
base::BarrierClosure(2, loop2.QuitClosure());
::testing::InSequence dummy;
EXPECT_CALL(*connection->connection_callbacks, Complete(kTransactionId))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(std::move(quit_closure2)));
connection->database.Bind(std::move(pending_database));
ASSERT_TRUE(connection->database.is_bound());
ASSERT_TRUE(connection->version_change_transaction.is_bound());
connection->version_change_transaction->CreateObjectStore(
kObjectStoreId, kObjectStoreName, blink::IndexedDBKeyPath(), false);
connection->version_change_transaction->Commit(0);
loop2.Run();
// Close the connections to finish the test nicely.
connection.reset();
}
// TODO(crbug.com/40813013): Test is flaky on Mac in debug.
#if BUILDFLAG(IS_MAC) && !defined(NDEBUG)
#define MAYBE_OpenNewConnectionWhileUpgrading \
DISABLED_OpenNewConnectionWhileUpgrading
#else
#define MAYBE_OpenNewConnectionWhileUpgrading OpenNewConnectionWhileUpgrading
#endif
TEST_P(IndexedDBTest, MAYBE_OpenNewConnectionWhileUpgrading) {
const int64_t kDBVersion = 1;
const int64_t kTransactionId = 1;
const int64_t kObjectStoreId = 10;
const char16_t kObjectStoreName[] = u"os";
std::unique_ptr<TestDatabaseConnection> connection1;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database1;
IndexedDBDatabaseMetadata metadata1;
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
base::RunLoop loop;
// Open connection 1, and expect the upgrade needed.
connection1 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), url::Origin::Create(GURL(kOrigin)),
kDatabaseName, kDBVersion, kTransactionId);
EXPECT_CALL(
*connection1->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(""), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database1),
testing::SaveArg<4>(&metadata1),
QuitLoop(&loop)));
// Queue open request message.
connection1->Open(bounded_factory_remote.get());
loop.Run();
std::unique_ptr<TestDatabaseConnection> connection2;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database2;
IndexedDBDatabaseMetadata metadata2;
base::RunLoop loop2;
base::RepeatingClosure quit_closure2 =
base::BarrierClosure(3, loop2.QuitClosure());
connection2 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
0);
// Check that we're called in order and the second connection gets it's
// database after the first connection completes.
::testing::InSequence dummy;
EXPECT_CALL(*connection1->connection_callbacks, Complete(kTransactionId))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection1->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection2->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(true), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database2),
testing::SaveArg<1>(&metadata2),
RunClosure(std::move(quit_closure2))));
connection1->database.Bind(std::move(pending_database1));
ASSERT_TRUE(connection1->database.is_bound());
ASSERT_TRUE(connection1->version_change_transaction.is_bound());
// Open connection 2, but expect that we won't be called back.
connection2->Open(bounded_factory_remote.get());
// Create object store.
connection1->version_change_transaction->CreateObjectStore(
kObjectStoreId, kObjectStoreName, blink::IndexedDBKeyPath(), false);
connection1->version_change_transaction->Commit(0);
loop2.Run();
EXPECT_TRUE(pending_database2.is_valid());
EXPECT_EQ(connection2->version, metadata2.version);
EXPECT_EQ(connection2->db_name, metadata2.name);
// Close the connections to finish the test nicely.
connection1.reset();
connection2.reset();
}
MATCHER_P(IsCallbackError, error_code, "") {
if (arg->is_error_result() &&
arg->get_error_result()->error_code == error_code) {
return true;
}
return false;
}
// See https://crbug.com/989723 for more context, this test seems to flake.
TEST_P(IndexedDBTest, DISABLED_PutWithInvalidBlob) {
const int64_t kDBVersion = 1;
const int64_t kTransactionId = 1;
const int64_t kObjectStoreId = 10;
const char16_t kObjectStoreName[] = u"os";
std::unique_ptr<TestDatabaseConnection> connection;
IndexedDBDatabaseMetadata metadata;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
base::RunLoop loop;
// Open connection.
connection = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), url::Origin::Create(GURL(kOrigin)),
kDatabaseName, kDBVersion, kTransactionId);
EXPECT_CALL(
*connection->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(""), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database),
testing::SaveArg<4>(&metadata),
QuitLoop(&loop)));
// Queue open request message.
connection->Open(bounded_factory_remote.get());
loop.Run();
ASSERT_TRUE(pending_database.is_valid());
EXPECT_EQ(connection->version, metadata.version);
EXPECT_EQ(connection->db_name, metadata.name);
base::MockCallback<blink::mojom::IDBTransaction::PutCallback> put_callback;
base::RunLoop loop2;
base::RepeatingClosure quit_closure2 =
base::BarrierClosure(3, loop2.QuitClosure());
::testing::InSequence dummy;
EXPECT_CALL(put_callback,
Run(IsCallbackError(blink::mojom::IDBException::kUnknownError)))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(
*connection->connection_callbacks,
Abort(kTransactionId, blink::mojom::IDBException::kUnknownError, _))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection->open_callbacks,
Error(blink::mojom::IDBException::kAbortError, _))
.Times(1)
.WillOnce(RunClosure(std::move(quit_closure2)));
connection->database.Bind(std::move(pending_database));
ASSERT_TRUE(connection->database.is_bound());
ASSERT_TRUE(connection->version_change_transaction.is_bound());
connection->version_change_transaction->CreateObjectStore(
kObjectStoreId, kObjectStoreName, blink::IndexedDBKeyPath(), false);
// Call Put with an invalid blob.
std::vector<blink::mojom::IDBExternalObjectPtr> external_objects;
mojo::PendingRemote<blink::mojom::Blob> blob;
// Ignore the result of InitWithNewPipeAndPassReceiver, to end up with
// an invalid blob.
std::ignore = blob.InitWithNewPipeAndPassReceiver();
external_objects.push_back(blink::mojom::IDBExternalObject::NewBlobOrFile(
blink::mojom::IDBBlobInfo::New(std::move(blob), "fakeUUID",
std::u16string(), 100, nullptr)));
std::string value = "hello";
const char* value_data = value.data();
std::vector<uint8_t> value_vector(value_data, value_data + value.length());
auto new_value = blink::mojom::IDBValue::New();
new_value->bits = std::move(value_vector);
new_value->external_objects = std::move(external_objects);
connection->version_change_transaction->Put(
kObjectStoreId, std::move(new_value), IndexedDBKey(u"hello"),
blink::mojom::IDBPutMode::AddOnly, std::vector<IndexedDBIndexKeys>(),
put_callback.Get());
connection->version_change_transaction->Commit(0);
loop2.Run();
// Close the connection to finish the test nicely.
connection.reset();
}
// Flaky: crbug.com/772067
TEST_P(IndexedDBTest, DISABLED_NotifyIndexedDBListChanged) {
const int64_t kDBVersion1 = 1;
const int64_t kDBVersion2 = 2;
const int64_t kDBVersion3 = 3;
const int64_t kTransactionId1 = 1;
const int64_t kTransactionId2 = 2;
const int64_t kTransactionId3 = 3;
const int64_t kObjectStoreId = 10;
const int64_t kIndexId = 100;
const char16_t kObjectStoreName[] = u"os";
const char16_t kIndexName[] = u"index";
mojo::PendingReceiver<storage::mojom::IndexedDBObserver> receiver;
mojo::PendingRemote<storage::mojom::IndexedDBObserver> remote;
TestIndexedDBObserver observer(remote.InitWithNewPipeAndPassReceiver());
context()->AddObserver(std::move(remote));
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
// Open connection 1.
std::unique_ptr<TestDatabaseConnection> connection1;
IndexedDBDatabaseMetadata metadata1;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database1;
EXPECT_EQ(0, observer.notify_list_changed_count);
{
base::RunLoop loop;
connection1 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName,
kDBVersion1, kTransactionId1);
EXPECT_CALL(
*connection1->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database1),
testing::SaveArg<4>(&metadata1),
QuitLoop(&loop)));
// Queue open request message.
connection1->Open(bounded_factory_remote.get());
loop.Run();
}
EXPECT_TRUE(pending_database1.is_valid());
EXPECT_EQ(connection1->version, metadata1.version);
EXPECT_EQ(connection1->db_name, metadata1.name);
{
::testing::InSequence dummy;
base::RunLoop loop;
base::RepeatingClosure quit_closure =
base::BarrierClosure(2, loop.QuitClosure());
// Create object store and index.
connection1->database.Bind(std::move(pending_database1));
ASSERT_TRUE(connection1->database.is_bound());
ASSERT_TRUE(connection1->version_change_transaction.is_bound());
EXPECT_CALL(*connection1->connection_callbacks, Complete(kTransactionId1))
.Times(1)
.WillOnce(RunClosure(quit_closure));
EXPECT_CALL(*connection1->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(std::move(quit_closure)));
ASSERT_TRUE(connection1->database.is_bound());
connection1->version_change_transaction->CreateObjectStore(
kObjectStoreId, kObjectStoreName, blink::IndexedDBKeyPath(), false);
connection1->database->CreateIndex(kTransactionId1, kObjectStoreId,
kIndexId, kIndexName,
blink::IndexedDBKeyPath(), false, false);
connection1->version_change_transaction->Commit(0);
loop.Run();
}
EXPECT_EQ(2, observer.notify_list_changed_count);
// Open connection 2.
std::unique_ptr<TestDatabaseConnection> connection2;
IndexedDBDatabaseMetadata metadata2;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database2;
{
::testing::InSequence dummy;
base::RunLoop loop;
base::RepeatingClosure quit_closure =
base::BarrierClosure(2, loop.QuitClosure());
connection2 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), url::Origin::Create(GURL(kOrigin)),
kDatabaseName, kDBVersion2, kTransactionId2);
EXPECT_CALL(*connection2->open_callbacks,
MockedUpgradeNeeded(
IsAssociatedInterfacePtrInfoValid(true), kDBVersion1,
blink::mojom::IDBDataLoss::None, std::string(), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database2),
testing::SaveArg<4>(&metadata2),
QuitLoop(&loop)));
// Queue open request message.
connection2->Open(bounded_factory_remote.get());
loop.Run();
}
EXPECT_TRUE(pending_database2.is_valid());
EXPECT_EQ(connection2->version, metadata2.version);
EXPECT_EQ(connection2->db_name, metadata2.name);
{
::testing::InSequence dummy;
base::RunLoop loop;
base::RepeatingClosure quit_closure =
base::BarrierClosure(2, loop.QuitClosure());
// Delete index.
connection2->database.Bind(std::move(pending_database2));
ASSERT_TRUE(connection2->database.is_bound());
ASSERT_TRUE(connection2->version_change_transaction.is_bound());
EXPECT_CALL(*connection2->connection_callbacks, Complete(kTransactionId2))
.Times(1)
.WillOnce(RunClosure(quit_closure));
EXPECT_CALL(*connection2->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(std::move(quit_closure)));
ASSERT_TRUE(connection2->database.is_bound());
connection2->database->DeleteIndex(kTransactionId2, kObjectStoreId,
kIndexId);
connection2->version_change_transaction->Commit(0);
loop.Run();
}
EXPECT_EQ(3, observer.notify_list_changed_count);
// Open connection 3.
std::unique_ptr<TestDatabaseConnection> connection3;
IndexedDBDatabaseMetadata metadata3;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database3;
{
::testing::InSequence dummy;
base::RunLoop loop;
connection3 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName,
kDBVersion3, kTransactionId3);
EXPECT_CALL(*connection3->open_callbacks,
MockedUpgradeNeeded(
IsAssociatedInterfacePtrInfoValid(true), kDBVersion2,
blink::mojom::IDBDataLoss::None, std::string(), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database3),
testing::SaveArg<4>(&metadata3),
QuitLoop(&loop)));
// Queue open request message.
connection3->Open(bounded_factory_remote.get());
loop.Run();
}
EXPECT_TRUE(pending_database3.is_valid());
EXPECT_EQ(connection3->version, metadata3.version);
EXPECT_EQ(connection3->db_name, metadata3.name);
{
::testing::InSequence dummy;
base::RunLoop loop;
base::RepeatingClosure quit_closure =
base::BarrierClosure(2, loop.QuitClosure());
// Delete object store.
connection3->database.Bind(std::move(pending_database3));
ASSERT_TRUE(connection3->database.is_bound());
ASSERT_TRUE(connection3->version_change_transaction.is_bound());
EXPECT_CALL(*connection3->connection_callbacks, Complete(kTransactionId3))
.Times(1)
.WillOnce(RunClosure(quit_closure));
EXPECT_CALL(*connection3->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(std::move(quit_closure)));
ASSERT_TRUE(connection3->database.is_bound());
connection3->version_change_transaction->DeleteObjectStore(kObjectStoreId);
connection3->version_change_transaction->Commit(0);
loop.Run();
}
EXPECT_EQ(4, observer.notify_list_changed_count);
// Close the connections to finish the test nicely.
connection1.reset();
connection2.reset();
connection3.reset();
}
MATCHER(IsSuccessKey, "") {
return arg->is_key();
}
// The test is flaky. See https://crbug.com/324111895
TEST_P(IndexedDBTest, DISABLED_NotifyIndexedDBContentChanged) {
const int64_t kDBVersion1 = 1;
const int64_t kDBVersion2 = 2;
const int64_t kTransactionId1 = 1;
const int64_t kTransactionId2 = 2;
const int64_t kObjectStoreId = 10;
const char16_t kObjectStoreName[] = u"os";
mojo::PendingReceiver<storage::mojom::IndexedDBObserver> receiver;
mojo::PendingRemote<storage::mojom::IndexedDBObserver> remote;
TestIndexedDBObserver observer(remote.InitWithNewPipeAndPassReceiver());
context()->AddObserver(std::move(remote));
EXPECT_EQ(0, observer.notify_list_changed_count);
EXPECT_EQ(0, observer.notify_content_changed_count);
std::unique_ptr<TestDatabaseConnection> connection1;
IndexedDBDatabaseMetadata metadata1;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database1;
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
base::RunLoop loop;
// Open connection 1.
connection1 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), url::Origin::Create(GURL(kOrigin)),
kDatabaseName, kDBVersion1, kTransactionId1);
EXPECT_CALL(
*connection1->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database1),
testing::SaveArg<4>(&metadata1),
QuitLoop(&loop)));
// Queue open request message.
connection1->Open(bounded_factory_remote.get());
loop.Run();
EXPECT_TRUE(pending_database1.is_valid());
EXPECT_EQ(connection1->version, metadata1.version);
EXPECT_EQ(connection1->db_name, metadata1.name);
base::MockCallback<blink::mojom::IDBTransaction::PutCallback> put_callback;
// Add object store entry.
base::RunLoop loop2;
base::RepeatingClosure quit_closure2 =
base::BarrierClosure(3, loop2.QuitClosure());
::testing::InSequence dummy;
EXPECT_CALL(put_callback, Run(IsSuccessKey()))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection1->connection_callbacks, Complete(kTransactionId1))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection1->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(std::move(quit_closure2)));
connection1->database.Bind(std::move(pending_database1));
ASSERT_TRUE(connection1->database.is_bound());
ASSERT_TRUE(connection1->version_change_transaction.is_bound());
connection1->version_change_transaction->CreateObjectStore(
kObjectStoreId, kObjectStoreName, blink::IndexedDBKeyPath(), false);
std::string value = "value";
const char* value_data = value.data();
std::vector<uint8_t> value_vector(value_data, value_data + value.length());
auto new_value = blink::mojom::IDBValue::New();
new_value->bits = std::move(value_vector);
connection1->version_change_transaction->Put(
kObjectStoreId, std::move(new_value), IndexedDBKey(u"key"),
blink::mojom::IDBPutMode::AddOnly, std::vector<IndexedDBIndexKeys>(),
put_callback.Get());
connection1->version_change_transaction->Commit(0);
loop2.Run();
EXPECT_EQ(2, observer.notify_list_changed_count);
EXPECT_EQ(1, observer.notify_content_changed_count);
connection1.reset();
std::unique_ptr<TestDatabaseConnection> connection2;
IndexedDBDatabaseMetadata metadata2;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database2;
// Open connection 2.
base::RunLoop loop4;
connection2 = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion2,
kTransactionId2);
EXPECT_CALL(
*connection2->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true), kDBVersion1,
blink::mojom::IDBDataLoss::None, std::string(), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database2),
testing::SaveArg<4>(&metadata2),
QuitLoop(&loop4)));
// Queue open request message.
connection2->Open(bounded_factory_remote.get());
loop4.Run();
EXPECT_TRUE(pending_database2.is_valid());
EXPECT_EQ(connection2->version, metadata2.version);
EXPECT_EQ(connection2->db_name, metadata2.name);
// Clear object store.
base::RunLoop loop5;
base::RepeatingClosure quit_closure5 =
base::BarrierClosure(3, loop5.QuitClosure());
EXPECT_CALL(*connection2->connection_callbacks, Complete(kTransactionId2))
.Times(1)
.WillOnce(RunClosure(quit_closure5));
EXPECT_CALL(*connection2->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(RunClosure(quit_closure5));
connection2->database.Bind(std::move(pending_database2));
ASSERT_TRUE(connection2->database.is_bound());
ASSERT_TRUE(connection2->version_change_transaction.is_bound());
connection2->database->Clear(kTransactionId2, kObjectStoreId,
base::IgnoreArgs<bool>(quit_closure5));
connection2->version_change_transaction->Commit(0);
loop5.Run();
// +2 list changed, one for the transaction, the other for the ~DatabaseImpl
EXPECT_EQ(4, observer.notify_list_changed_count);
EXPECT_EQ(2, observer.notify_content_changed_count);
// Close the connection to finish the test nicely.
connection2.reset();
}
// The test is flaky. See https://crbug.com/324282438
TEST_P(IndexedDBTest, DISABLED_DatabaseOperationSequencing) {
const int64_t kDBVersion = 1;
const int64_t kTransactionId = 1;
const std::u16string kObjectStoreName1 = u"os1";
const std::u16string kObjectStoreName2 = u"os2";
const std::u16string kObjectStoreName3 = u"os3";
std::unique_ptr<TestDatabaseConnection> connection;
IndexedDBDatabaseMetadata metadata;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
// Bind the IDBFactory.
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
BindIndexedDBFactory(std::move(checker_remote),
bounded_factory_remote.BindNewPipeAndPassReceiver(),
storage::BucketInfo());
// Open the connection, which will initiate the "upgrade" transaction.
base::RunLoop loop;
// Open connection.
connection = std::make_unique<TestDatabaseConnection>(
context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
kTransactionId);
EXPECT_CALL(
*connection->open_callbacks,
MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
IndexedDBDatabaseMetadata::NO_VERSION,
blink::mojom::IDBDataLoss::None, std::string(""), _))
.WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database),
testing::SaveArg<4>(&metadata),
QuitLoop(&loop)));
// Queue open request message.
connection->Open(bounded_factory_remote.get());
loop.Run();
ASSERT_TRUE(pending_database.is_valid());
EXPECT_EQ(connection->version, metadata.version);
EXPECT_EQ(connection->db_name, metadata.name);
EXPECT_EQ(0ULL, metadata.object_stores.size());
// Within the "upgrade" transaction, create/delete/create object store. This
// should leave only one store around if everything is processed in the
// correct order.
IndexedDBDatabaseMetadata metadata2;
int64_t object_store_id = 1001;
base::RunLoop loop2;
base::RepeatingClosure quit_closure2 =
base::BarrierClosure(2, loop2.QuitClosure());
::testing::InSequence dummy;
EXPECT_CALL(*connection->connection_callbacks, Complete(kTransactionId))
.Times(1)
.WillOnce(RunClosure(quit_closure2));
EXPECT_CALL(*connection->open_callbacks,
MockedOpenSuccess(IsAssociatedInterfacePtrInfoValid(false), _))
.Times(1)
.WillOnce(testing::DoAll(testing::SaveArg<1>(&metadata2),
RunClosure(std::move(quit_closure2))));
connection->database.Bind(std::move(pending_database));
ASSERT_TRUE(connection->database.is_bound());
ASSERT_TRUE(connection->version_change_transaction.is_bound());
// This will cause a CreateObjectStoreOperation to be queued and
// run synchronously...
connection->version_change_transaction->CreateObjectStore(
++object_store_id, kObjectStoreName1, blink::IndexedDBKeyPath(),
/*auto_increment=*/false);
// The following operations will queue operations, but the
// operations will run asynchronously.
// First, delete the previous store. Ensure that this succeeds
// even if the previous action completed synchronously.
connection->version_change_transaction->DeleteObjectStore(object_store_id);
// Ensure that a create/delete pair where both parts are queued
// succeeds.
connection->version_change_transaction->CreateObjectStore(
++object_store_id, kObjectStoreName2, blink::IndexedDBKeyPath(),
/*auto_increment=*/false);
connection->version_change_transaction->DeleteObjectStore(object_store_id);
// This store is left over, just to verify that everything
// ran correctly.
connection->version_change_transaction->CreateObjectStore(
++object_store_id, kObjectStoreName3, blink::IndexedDBKeyPath(),
/*auto_increment=*/false);
connection->version_change_transaction->Commit(0);
loop2.Run();
EXPECT_EQ(1ULL, metadata2.object_stores.size());
EXPECT_EQ(metadata2.object_stores[object_store_id].name, kObjectStoreName3);
// Close the connection to finish the test nicely.
connection.reset();
}
TEST_P(IndexedDBTest, ClearSessionOnlyDatabases) {
base::FilePath normal_path_first_party;
base::FilePath session_only_path_first_party;
base::FilePath session_only_subdomain_path_first_party;
base::FilePath normal_path_third_party;
base::FilePath session_only_path_third_party;
base::FilePath session_only_subdomain_path_third_party;
base::FilePath inverted_normal_path_third_party;
base::FilePath inverted_session_only_path_third_party;
base::FilePath inverted_session_only_subdomain_path_third_party;
normal_path_first_party =
GetFilePathForTesting(kNormalFirstPartyBucketLocator);
session_only_path_first_party =
GetFilePathForTesting(kSessionOnlyFirstPartyBucketLocator);
session_only_subdomain_path_first_party =
GetFilePathForTesting(kSessionOnlySubdomainFirstPartyBucketLocator);
normal_path_third_party =
GetFilePathForTesting(kNormalThirdPartyBucketLocator);
session_only_path_third_party =
GetFilePathForTesting(kSessionOnlyThirdPartyBucketLocator);
session_only_subdomain_path_third_party =
GetFilePathForTesting(kSessionOnlySubdomainThirdPartyBucketLocator);
inverted_normal_path_third_party =
GetFilePathForTesting(kInvertedNormalThirdPartyBucketLocator);
inverted_session_only_path_third_party =
GetFilePathForTesting(kInvertedSessionOnlyThirdPartyBucketLocator);
inverted_session_only_subdomain_path_third_party = GetFilePathForTesting(
kInvertedSessionOnlySubdomainThirdPartyBucketLocator);
if (IsThirdPartyStoragePartitioningEnabled()) {
EXPECT_NE(normal_path_first_party, normal_path_third_party);
EXPECT_NE(session_only_path_first_party, session_only_path_third_party);
EXPECT_NE(session_only_subdomain_path_first_party,
session_only_subdomain_path_third_party);
EXPECT_NE(inverted_normal_path_third_party,
inverted_session_only_path_third_party);
EXPECT_NE(inverted_normal_path_third_party,
inverted_session_only_subdomain_path_third_party);
} else {
EXPECT_EQ(normal_path_first_party, normal_path_third_party);
EXPECT_EQ(session_only_path_first_party, session_only_path_third_party);
EXPECT_EQ(session_only_subdomain_path_first_party,
session_only_subdomain_path_third_party);
EXPECT_EQ(inverted_normal_path_third_party,
inverted_session_only_path_third_party);
EXPECT_EQ(inverted_normal_path_third_party,
inverted_session_only_subdomain_path_third_party);
}
ASSERT_TRUE(base::CreateDirectory(normal_path_first_party));
ASSERT_TRUE(base::CreateDirectory(session_only_path_first_party));
ASSERT_TRUE(base::CreateDirectory(session_only_subdomain_path_first_party));
ASSERT_TRUE(base::CreateDirectory(normal_path_third_party));
ASSERT_TRUE(base::CreateDirectory(session_only_path_third_party));
ASSERT_TRUE(base::CreateDirectory(session_only_subdomain_path_third_party));
ASSERT_TRUE(base::CreateDirectory(inverted_normal_path_third_party));
ASSERT_TRUE(base::CreateDirectory(inverted_session_only_path_third_party));
ASSERT_TRUE(
base::CreateDirectory(inverted_session_only_subdomain_path_third_party));
base::RunLoop run_loop;
context()->ForceInitializeFromFilesForTesting(run_loop.QuitClosure());
run_loop.Run();
IndexedDBContextImpl::Shutdown(std::move(context_));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(base::DirectoryExists(normal_path_first_party));
EXPECT_TRUE(base::DirectoryExists(session_only_path_first_party));
EXPECT_FALSE(base::DirectoryExists(session_only_subdomain_path_first_party));
EXPECT_TRUE(base::DirectoryExists(normal_path_third_party));
EXPECT_TRUE(base::DirectoryExists(session_only_path_third_party));
EXPECT_FALSE(base::DirectoryExists(session_only_subdomain_path_third_party));
EXPECT_TRUE(base::DirectoryExists(inverted_normal_path_third_party));
// When storage partitioning is enabled these will be deleted because they
// have a matching top-level site, but otherwise they won't be because the
// deletion logic only considers the origin.
if (IsThirdPartyStoragePartitioningEnabled()) {
EXPECT_FALSE(base::DirectoryExists(inverted_session_only_path_third_party));
EXPECT_FALSE(base::DirectoryExists(
inverted_session_only_subdomain_path_third_party));
} else {
EXPECT_TRUE(base::DirectoryExists(inverted_session_only_path_third_party));
EXPECT_TRUE(base::DirectoryExists(
inverted_session_only_subdomain_path_third_party));
}
}
TEST_P(IndexedDBTest, SetForceKeepSessionState) {
base::FilePath normal_path_first_party;
base::FilePath session_only_path_first_party;
base::FilePath normal_path_third_party;
base::FilePath session_only_path_third_party;
// Save session state. This should bypass the destruction-time deletion.
context()->SetForceKeepSessionState();
normal_path_first_party =
GetFilePathForTesting(kNormalFirstPartyBucketLocator);
session_only_path_first_party =
GetFilePathForTesting(kSessionOnlyFirstPartyBucketLocator);
normal_path_third_party =
GetFilePathForTesting(kNormalThirdPartyBucketLocator);
session_only_path_third_party =
GetFilePathForTesting(kSessionOnlyThirdPartyBucketLocator);
if (IsThirdPartyStoragePartitioningEnabled()) {
EXPECT_NE(normal_path_first_party, normal_path_third_party);
EXPECT_NE(session_only_path_first_party, session_only_path_third_party);
} else {
EXPECT_EQ(normal_path_first_party, normal_path_third_party);
EXPECT_EQ(session_only_path_first_party, session_only_path_third_party);
}
ASSERT_TRUE(base::CreateDirectory(normal_path_first_party));
ASSERT_TRUE(base::CreateDirectory(session_only_path_first_party));
ASSERT_TRUE(base::CreateDirectory(normal_path_third_party));
ASSERT_TRUE(base::CreateDirectory(session_only_path_third_party));
context()->ForceInitializeFromFilesForTesting(base::DoNothing());
base::RunLoop().RunUntilIdle();
IndexedDBContextImpl::Shutdown(std::move(context_));
base::RunLoop().RunUntilIdle();
// No data was cleared because of SetForceKeepSessionState.
EXPECT_TRUE(base::DirectoryExists(normal_path_first_party));
EXPECT_TRUE(base::DirectoryExists(session_only_path_first_party));
EXPECT_TRUE(base::DirectoryExists(normal_path_third_party));
EXPECT_TRUE(base::DirectoryExists(session_only_path_third_party));
}
// Tests that parameterize whether they act on first or third party storage key
// buckets.
using IndexedDBTestFirstOrThirdParty = IndexedDBTest;
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
IndexedDBTestFirstOrThirdParty,
testing::Combine(
/*enable third party storage partitioning*/ testing::Bool(),
/*test with third party storage key*/ testing::Bool(),
/*enable backing store sharding*/ testing::Bool()));
// Verifies that the IDB connection is force closed and the directory is deleted
// when the bucket is deleted.
TEST_P(IndexedDBTestFirstOrThirdParty, ForceCloseOpenDatabasesOnDelete) {
storage::BucketInfo bucket_info;
VerifyForcedClosedCalled(
base::BindOnce(base::IgnoreResult(&IndexedDBTest::DeleteBucket),
base::Unretained(this), &bucket_info),
&bucket_info);
// Additionally, the directory should be deleted.
base::FilePath test_path =
GetFilePathForTesting(bucket_info.ToBucketLocator());
EXPECT_FALSE(base::DirectoryExists(test_path));
}
// Verifies that the IDB connection is force closed when the backing store has
// an error.
TEST_P(IndexedDBTestFirstOrThirdParty, ForceCloseOpenDatabasesOnCommitFailure) {
storage::BucketInfo bucket_info;
VerifyForcedClosedCalled(
BackingStoresSharded()
? base::BindOnce(
[](IndexedDBContextImpl* context,
storage::BucketInfo* bucket_info) {
context->GetShardedBucketContextForTesting(bucket_info->id)
->AsyncCall(&IndexedDBBucketContext::OnDatabaseError)
.WithArgs(leveldb::Status::NotSupported(
"operation not supported"),
std::string());
},
context(), &bucket_info)
: base::BindOnce(
[](IndexedDBContextImpl* context,
storage::BucketInfo* bucket_info) {
context->GetBucketContextForTesting(bucket_info->id)
->OnDatabaseError(leveldb::Status::NotSupported(
"operation not supported"),
{});
},
context(), &bucket_info),
&bucket_info);
}
// Verifies that the IDB connection is force closed when the database is deleted
// via the mojo API.
TEST_P(IndexedDBTestFirstOrThirdParty,
ForceCloseOpenDatabasesOnDeleteDatabase) {
storage::BucketInfo bucket_info;
VerifyForcedClosedCalled(
base::BindOnce(
[](mojo::Remote<blink::mojom::IDBFactory>* factory_remote) {
MockMojoIndexedDBFactoryClient delete_client;
(*factory_remote)
->DeleteDatabase(delete_client.CreateInterfacePtrAndBind(),
u"opendb",
/*force_close=*/true);
},
&this->factory_remote_),
&bucket_info);
base::FilePath test_path =
GetFilePathForTesting(bucket_info.ToBucketLocator());
EXPECT_TRUE(base::DirectoryExists(test_path));
}
TEST(PartitionedLockManager, TestRangeDifferences) {
PartitionedLockId lock_id_db1;
PartitionedLockId lock_id_db2;
PartitionedLockId lock_id_db1_os1;
PartitionedLockId lock_id_db1_os2;
for (int64_t i = 0; i < 512; ++i) {
lock_id_db1 = GetDatabaseLockId(
base::ASCIIToUTF16(base::StringPrintf("%" PRIx64, i)));
lock_id_db2 = GetDatabaseLockId(
base::ASCIIToUTF16(base::StringPrintf("%" PRIx64, i + 1)));
lock_id_db1_os1 = GetObjectStoreLockId(i, i);
lock_id_db1_os2 = GetObjectStoreLockId(i, i + 1);
EXPECT_NE(lock_id_db1, lock_id_db2);
EXPECT_NE(lock_id_db1, lock_id_db1_os1);
EXPECT_NE(lock_id_db1, lock_id_db1_os2);
EXPECT_NE(lock_id_db1_os1, lock_id_db1_os2);
EXPECT_NE(lock_id_db1_os1, lock_id_db2);
EXPECT_NE(lock_id_db1_os2, lock_id_db2);
}
}
TEST_P(IndexedDBTest, BasicFactoryCreationAndTearDown) {
context_->ForceSingleThreadForTesting();
const blink::StorageKey storage_key_1 =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
storage::BucketInfo bucket_1 = GetOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(storage_key_1));
BucketLocator bucket_locator_1 = bucket_1.ToBucketLocator();
auto file_1 =
context_->GetLevelDBPath(bucket_locator_1).AppendASCII("1.json");
ASSERT_TRUE(CreateDirectory(file_1.DirName()));
ASSERT_TRUE(base::WriteFile(file_1, std::string(10, 'a')));
const blink::StorageKey storage_key_2 =
blink::StorageKey::CreateFromStringForTesting("http://localhost:82");
storage::BucketInfo bucket_2 = GetOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(storage_key_2));
BucketLocator bucket_locator_2 = bucket_2.ToBucketLocator();
auto file_2 =
context_->GetLevelDBPath(bucket_locator_2).AppendASCII("2.json");
ASSERT_TRUE(CreateDirectory(file_2.DirName()));
ASSERT_TRUE(base::WriteFile(file_2, std::string(100, 'a')));
const blink::StorageKey storage_key_3 =
blink::StorageKey::CreateFromStringForTesting("http://localhost2:82");
storage::BucketInfo bucket_3 = GetOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(storage_key_3));
BucketLocator bucket_locator_3 = bucket_3.ToBucketLocator();
auto file_3 =
context_->GetLevelDBPath(bucket_locator_3).AppendASCII("3.json");
ASSERT_TRUE(CreateDirectory(file_3.DirName()));
ASSERT_TRUE(base::WriteFile(file_3, std::string(1000, 'a')));
const blink::StorageKey storage_key_4 = blink::StorageKey::Create(
storage_key_1.origin(), net::SchemefulSite(storage_key_3.origin()),
blink::mojom::AncestorChainBit::kCrossSite);
storage::BucketInfo bucket_4 = GetOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(storage_key_4));
BucketLocator bucket_locator_4 = bucket_4.ToBucketLocator();
auto file_4 =
context_->GetLevelDBPath(bucket_locator_4).AppendASCII("4.json");
ASSERT_TRUE(CreateDirectory(file_4.DirName()));
ASSERT_TRUE(base::WriteFile(file_4, std::string(10000, 'a')));
const blink::StorageKey storage_key_5 = storage_key_1;
storage::BucketInitParams params(storage_key_5, "inbox");
storage::BucketInfo bucket_5 = GetOrCreateBucket(params);
BucketLocator bucket_locator_5 = bucket_5.ToBucketLocator();
auto file_5 =
context_->GetLevelDBPath(bucket_locator_5).AppendASCII("5.json");
ASSERT_TRUE(CreateDirectory(file_5.DirName()));
ASSERT_TRUE(base::WriteFile(file_5, std::string(20000, 'a')));
EXPECT_NE(file_5.DirName(), file_1.DirName());
GetOrCreateBucketContext(bucket_1, context()->GetDataPath(bucket_locator_1))
.InitBackingStoreIfNeeded(true);
GetOrCreateBucketContext(bucket_2, context()->GetDataPath(bucket_locator_2))
.InitBackingStoreIfNeeded(true);
GetOrCreateBucketContext(bucket_3, context()->GetDataPath(bucket_locator_3))
.InitBackingStoreIfNeeded(true);
GetOrCreateBucketContext(bucket_4, context()->GetDataPath(bucket_locator_4))
.InitBackingStoreIfNeeded(true);
GetOrCreateBucketContext(bucket_5, context()->GetDataPath(bucket_locator_5))
.InitBackingStoreIfNeeded(true);
int64_t bucket_size_1 = base::ComputeDirectorySize(file_1.DirName());
int64_t bucket_size_4 = base::ComputeDirectorySize(file_4.DirName());
int64_t bucket_size_5 = base::ComputeDirectorySize(file_5.DirName());
if (IsThirdPartyStoragePartitioningEnabled()) {
// If third party storage partitioning is on, additional space is taken
// by supporting files for the independent buckets.
EXPECT_NE(bucket_size_1, bucket_size_4);
}
EXPECT_NE(bucket_size_1, bucket_size_5);
if (IsThirdPartyStoragePartitioningEnabled()) {
EXPECT_EQ(5ul, context_->GetOpenBucketIdsForTesting().size());
} else {
EXPECT_EQ(4ul, context_->GetOpenBucketIdsForTesting().size());
}
}
TEST_P(IndexedDBTest, CloseSequenceStarts) {
context_->ForceSingleThreadForTesting();
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
const storage::BucketId bucket_id =
bucket_context_handle->bucket_locator().id;
bucket_context_handle.Release();
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/true);
EXPECT_TRUE(GetBucketContext(bucket_id)->IsClosing());
context_->ForceClose(bucket_id, {}, base::DoNothing());
VerifyBucketContextWaitIfNeeded(bucket_id, /*expected_context_exists=*/false);
}
// Similar to the above, but installs a receiver which prevents the bucket
// context from being destroyed.
TEST_P(IndexedDBTest, CloseWithReceiversActive) {
// Create bucket context.
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
const storage::BucketId bucket_id =
bucket_context_handle->bucket_locator().id;
// Connect an IDBFactory mojo client.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
bucket_context_handle->AddReceiver(
std::move(checker_remote), /*client_token=*/{},
factory_remote.BindNewPipeAndPassReceiver());
// The bucket context and the backing store should exist.
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/true);
// The last handle to the bucket context is released and the grace period
// elapses.
bucket_context_handle.Release();
task_environment_.FastForwardBy(base::Seconds(2));
// This destroys the backing store, but the bucket context itself still
// exists...
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/false);
// ...until the last mojo client is disconnected.
factory_remote.reset();
task_environment_.RunUntilIdle();
VerifyBucketContext(bucket_id, /*expected_context_exists=*/false);
}
// Similar to the above, but reverses the order of receiver disconnection and
// handle destruction.
TEST_P(IndexedDBTest, CloseWithReceiversInactive) {
// Create bucket context.
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
const storage::BucketId bucket_id =
bucket_context_handle->bucket_locator().id;
// Connect an IDBFactory mojo client.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
bucket_context_handle->AddReceiver(
std::move(checker_remote), /*client_token=*/{},
factory_remote.BindNewPipeAndPassReceiver());
// The bucket context and the backing store should exist.
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/true);
// The last mojo client is disconnected.
factory_remote.reset();
task_environment_.RunUntilIdle();
// The bucket context and the backing store should still exist.
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/true);
// The last handle to the bucket context is released and the grace period
// elapses.
bucket_context_handle.Release();
task_environment_.FastForwardBy(base::Seconds(2));
VerifyBucketContext(bucket_id, /*expected_context_exists=*/false);
}
TEST_P(IndexedDBTest, PreCloseTasksStart) {
{
// Open a connection & immediately release it to cause the closing sequence
// to start.
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
storage::BucketId bucket_id = bucket_context_handle->bucket_locator().id;
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_context_handle->bucket_locator()));
bucket_context_handle.Release();
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/true);
EXPECT_TRUE(GetBucketContext(bucket_id)->IsClosing());
EXPECT_EQ(IndexedDBBucketContext::ClosingState::kPreCloseGracePeriod,
GetBucketContext(bucket_id)->closing_stage());
task_environment_.FastForwardBy(base::Seconds(2));
// The factory should be closed, as the pre close tasks are delayed.
VerifyBucketContext(bucket_id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/false);
}
// Move the clock to run the tasks in the next close sequence.
// NOTE: The constants rate-limiting sweeps and compaction are currently the
// same. This test may need to be restructured if these values diverge.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestGlobalSweepFromNow);
// Note that once the closing sequence has started, as is the case in the next
// block, and if the test does anything to spin the message loop, such as
// using a future to get the bucket context, the bucket context will be
// destroyed. For that reason, the bucket context pointer is stored in
// `bucket_context`.
IndexedDBBucketContext* bucket_context = nullptr;
{
// Open a connection & immediately release it to cause the closing sequence
// to start again.
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
bucket_context = bucket_context_handle.bucket_context();
bucket_context_handle.Release();
// Manually execute the timer so that the PreCloseTaskList task doesn't also
// run.
bucket_context->close_timer()->FireNow();
// The pre-close tasks should be running now.
EXPECT_EQ(IndexedDBBucketContext::ClosingState::kRunningPreCloseTasks,
bucket_context->closing_stage());
ASSERT_TRUE(bucket_context->pre_close_task_queue());
EXPECT_TRUE(bucket_context->pre_close_task_queue()->started());
}
{
// Stop sweep by opening a connection.
IndexedDBBucketContextHandle bucket_context_handle(*bucket_context);
storage::BucketId bucket_id = bucket_context_handle->bucket_locator().id;
EXPECT_FALSE(bucket_context_handle->pre_close_task_queue());
// Move clock forward to trigger next sweep, but storage key has longer
// sweep minimum, so no tasks should execute.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestGlobalSweepFromNow);
bucket_context_handle.Release();
EXPECT_EQ(IndexedDBBucketContext::ClosingState::kPreCloseGracePeriod,
bucket_context->closing_stage());
// Manually execute the timer so that the PreCloseTaskList task doesn't also
// run.
bucket_context->close_timer()->FireNow();
ASSERT_TRUE(context_->BucketContextExists(bucket_id));
EXPECT_TRUE(!!bucket_context->backing_store());
VerifyBucketContextWaitIfNeeded(bucket_id,
/*expected_context_exists=*/false);
}
{
// Finally, move the clock forward so the storage key should allow a sweep.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestBucketSweepFromNow);
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
bucket_context = bucket_context_handle.bucket_context();
storage::BucketId bucket_id = bucket_context_handle->bucket_locator().id;
bucket_context_handle.Release();
bucket_context->close_timer()->FireNow();
ASSERT_TRUE(context_->BucketContextExists(bucket_id));
EXPECT_EQ(IndexedDBBucketContext::ClosingState::kRunningPreCloseTasks,
bucket_context->closing_stage());
ASSERT_TRUE(bucket_context->pre_close_task_queue());
EXPECT_TRUE(bucket_context->pre_close_task_queue()->started());
}
}
TEST_P(IndexedDBTest, TombstoneSweeperTiming) {
// Open a connection.
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
EXPECT_FALSE(bucket_context_handle->ShouldRunTombstoneSweeper());
// Move the clock to run the tasks in the next close sequence.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestGlobalSweepFromNow);
EXPECT_TRUE(bucket_context_handle->ShouldRunTombstoneSweeper());
// Move clock forward to trigger next sweep, but storage key has longer
// sweep minimum, so no tasks should execute.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestGlobalSweepFromNow);
EXPECT_FALSE(bucket_context_handle->ShouldRunTombstoneSweeper());
// Finally, move the clock forward so the storage key should allow a sweep.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestBucketSweepFromNow);
EXPECT_TRUE(bucket_context_handle->ShouldRunTombstoneSweeper());
}
TEST_P(IndexedDBTest, CompactionTaskTiming) {
// Open a connection.
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
bucket_context_handle->InitBackingStoreIfNeeded(/*create_if_missing=*/true);
EXPECT_FALSE(bucket_context_handle->ShouldRunCompaction());
// Move the clock to run the tasks in the next close sequence.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestGlobalCompactionFromNow);
EXPECT_TRUE(bucket_context_handle->ShouldRunCompaction());
// Move clock forward to trigger next compaction, but storage key has longer
// compaction minimum, so no tasks should execute.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestGlobalCompactionFromNow);
EXPECT_FALSE(bucket_context_handle->ShouldRunCompaction());
// Finally, move the clock forward so the storage key should allow a
// compaction.
task_environment_.FastForwardBy(
IndexedDBBucketContext::kMaxEarliestBucketCompactionFromNow);
EXPECT_TRUE(bucket_context_handle->ShouldRunCompaction());
}
TEST_P(IndexedDBTest, InMemoryFactoriesStay) {
SetUpInMemoryContext();
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
BucketLocator bucket_locator = bucket_context_handle->bucket_locator();
EXPECT_TRUE(bucket_context_handle->backing_store()->in_memory());
IndexedDBBucketContext* bucket_context =
bucket_context_handle.bucket_context();
bucket_context_handle.Release();
RunPostedTasks();
EXPECT_TRUE(context_->BucketContextExists(bucket_locator.id));
EXPECT_FALSE(bucket_context->IsClosing());
context_->ForceClose(
bucket_locator.id,
storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE,
base::DoNothing());
if (BackingStoresSharded()) {
// Verify the in-memory factory sticks around. Since it would be destroyed
// asynchronously, there's no reliable point in time to verify that
// destruction *hasn't* happened, so just wait a bit before verifying.
RunPostedTasks();
RunPostedTasks();
RunPostedTasks();
}
VerifyBucketContext(bucket_locator.id, /*expected_context_exists=*/true,
/*expected_backing_store_exists=*/true);
context_->ForceClose(
bucket_locator.id,
storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN,
base::DoNothing());
VerifyBucketContextWaitIfNeeded(bucket_locator.id,
/*expected_context_exists=*/false);
}
TEST_P(IndexedDBTest, TooLongOrigin) {
context_->ForceSingleThreadForTesting();
base::FilePath temp_dir =
context()->GetFirstPartyDataPathForTesting().DirName();
int limit = base::GetMaximumPathComponentLength(temp_dir);
EXPECT_GT(limit, 0);
std::string origin(limit + 1, 'x');
const blink::StorageKey too_long_storage_key =
blink::StorageKey::CreateFromStringForTesting("http://" + origin +
":81/");
storage::BucketInfo bucket_info = GetOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(too_long_storage_key));
BucketLocator bucket_locator = bucket_info.ToBucketLocator();
IndexedDBBucketContextHandle bucket_context_handle(GetOrCreateBucketContext(
ToBucketInfo(bucket_locator), context()->GetDataPath(bucket_locator)));
leveldb::Status s;
std::tie(s, std::ignore, std::ignore) =
bucket_context_handle->InitBackingStoreIfNeeded(
/*create_if_missing=*/true);
EXPECT_TRUE(s.IsIOError());
}
TEST_P(IndexedDBTest, FactoryForceClose) {
IndexedDBBucketContextHandle bucket_context_handle = CreateBucketHandle();
BucketLocator bucket_locator = bucket_context_handle->bucket_locator();
bucket_context_handle->ForceClose(/*doom=*/false);
IndexedDBBucketContext* bucket_context =
bucket_context_handle.bucket_context();
bucket_context_handle.Release();
ASSERT_TRUE(context_->BucketContextExists(bucket_locator.id));
EXPECT_TRUE(!!bucket_context->backing_store());
VerifyBucketContextWaitIfNeeded(bucket_locator.id,
/*expected_context_exists=*/false);
}
// This test aims to verify the behavior of
// IndexedDBBucketContext::Delegate::on_receiver_bounced.
TEST_P(IndexedDBTest, CloseThenAddReceiver) {
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
// Trigger the bucket context to be created.
mojo::Remote<blink::mojom::IDBFactory> factory_remote1;
BindIndexedDBFactory(
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>(),
factory_remote1.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
ASSERT_TRUE(context()->BucketContextExists(bucket_locator.id));
// Remove the factory binding, and since there is no backing store yet, this
// should trigger the destruction of the bucket context.
factory_remote1.reset();
if (BackingStoresSharded()) {
// However, the bucket context still exists for now because shutdown is not
// synchronous.
ASSERT_TRUE(context()->BucketContextExists(bucket_locator.id));
// Bind another IDB factory. It's important that this is called
// synchronously because it will initially attempt to bind to the existing
// bucket context above, but that fails in
// IndexedDBBucketContext::AddReceiver().
mojo::Remote<blink::mojom::IDBFactory> factory_remote2;
BindIndexedDBFactory(
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>(),
factory_remote2.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Round trip a message through the new mojo pipe to verify that it is set
// up correctly.
factory_remote2.FlushForTesting();
// It would be nice to re-verify that the new BucketContext is not the same
// as the old one, but there's no good way to identify them through mojo and
// no guarantee their memory addresses are different either.
} else {
ASSERT_TRUE(context()->BucketContextExists(bucket_locator.id));
RunPostedTasks();
ASSERT_FALSE(context()->BucketContextExists(bucket_locator.id));
}
}
// Tests that the backing store is closed when the connection is closed during
// upgrade.
TEST_P(IndexedDBTest, ConnectionCloseDuringUpgrade) {
context_->ForceSingleThreadForTesting();
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
// Bind the IDBFactory.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Now create a database and thus the backing store.
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
EXPECT_CALL(client, MockedUpgradeNeeded)
.WillOnce(
testing::DoAll(MoveArgPointee<0>(&pending_database),
::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(), u"db",
/*version=*/1,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/1);
run_loop.Run();
ASSERT_TRUE(context_->BucketContextExists(bucket_locator.id));
EXPECT_FALSE(GetBucketContext(bucket_locator.id)->IsClosing());
// Drop the connection.
pending_database.reset();
factory_remote.FlushForTesting();
EXPECT_TRUE(GetBucketContext(bucket_locator.id)->IsClosing());
}
TEST_P(IndexedDBTest, DeleteDatabase) {
context_->ForceSingleThreadForTesting();
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
// Bind the IDBFactory.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Don't create a backing store if one doesn't exist.
{
// Delete db.
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
EXPECT_CALL(client, DeleteSuccess)
.WillOnce(
testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->DeleteDatabase(client.CreateInterfacePtrAndBind(), u"db",
/*force_close=*/false);
run_loop.Run();
// Backing store shouldn't exist.
ASSERT_TRUE(context_->BucketContextExists(bucket_locator.id));
EXPECT_FALSE(GetBucketContext(bucket_locator.id)->backing_store());
}
// Now create a database and thus the backing store.
{
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
EXPECT_CALL(client, MockedOpenSuccess)
.WillOnce(::base::test::RunClosure(run_loop.QuitClosure()));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(), u"db",
/*version=*/0,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/1);
run_loop.Run();
}
// Delete the database now that the backing store actually exists.
{
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
EXPECT_CALL(client, DeleteSuccess)
.WillOnce(
testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->DeleteDatabase(client.CreateInterfacePtrAndBind(), u"db",
/*force_close=*/false);
run_loop.Run();
// Since there are no more references the factory should be closing.
ASSERT_TRUE(context_->BucketContextExists(bucket_locator.id));
EXPECT_TRUE(GetBucketContext(bucket_locator.id)->IsClosing());
}
}
TEST_P(IndexedDBTest, GetDatabaseNames_NoFactory) {
context_->ForceSingleThreadForTesting();
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
// Bind the IDBFactory.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Don't create a backing store if one doesn't exist.
{
base::test::TestFuture<std::vector<blink::mojom::IDBNameAndVersionPtr>,
blink::mojom::IDBErrorPtr>
info_future;
factory_remote->GetDatabaseInfo(info_future.GetCallback());
ASSERT_TRUE(info_future.Wait());
EXPECT_FALSE(GetBucketContext(bucket_locator.id)->backing_store());
}
// Now create a database and thus the backing store.
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
// It's necessary to hang onto the database connection or the connection
// will shut itself down and the backing store will close on its own.
mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
EXPECT_CALL(client, MockedOpenSuccess)
.WillOnce(
testing::DoAll(MoveArgPointee<0>(&pending_database),
::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(), u"db",
/*version=*/0,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/1);
run_loop.Run();
// GetDatabaseInfo didn't create the factory, so it shouldn't close it.
{
base::test::TestFuture<std::vector<blink::mojom::IDBNameAndVersionPtr>,
blink::mojom::IDBErrorPtr>
info_future;
factory_remote->GetDatabaseInfo(info_future.GetCallback());
ASSERT_TRUE(info_future.Wait());
ASSERT_TRUE(context_->BucketContextExists(bucket_locator.id));
EXPECT_FALSE(GetBucketContext(bucket_locator.id)->IsClosing());
}
}
TEST_P(IndexedDBTest, QuotaErrorOnDiskFull) {
leveldb_env::SetDBFactoryForTesting(base::BindRepeating(
[](const leveldb_env::Options& options, const std::string& name,
std::unique_ptr<leveldb::DB>* dbptr) {
return leveldb_env::MakeIOError("foobar", "disk full",
leveldb_env::MethodID::kCreateDir,
base::File::FILE_ERROR_NO_SPACE);
}));
// Bind the IDBFactory.
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Expect an error when opening.
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
EXPECT_CALL(client, Error)
.WillOnce(
testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(), u"db",
/*version=*/1,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/1);
run_loop.Run();
// A disk full error results in an error reported to the quota system.
ASSERT_EQ(1U, quota_manager_->write_error_tracker().size());
EXPECT_EQ(storage_key, quota_manager_->write_error_tracker().begin()->first);
EXPECT_EQ(1, quota_manager_->write_error_tracker().begin()->second);
leveldb_env::SetDBFactoryForTesting({});
}
TEST_P(IndexedDBTest, DatabaseFailedOpen) {
context_->ForceSingleThreadForTesting();
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
const std::u16string db_name(u"db");
// Bind the IDBFactory.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Open at version 2.
{
const int64_t db_version = 2;
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
EXPECT_CALL(client, MockedUpgradeNeeded)
.WillOnce(
testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(),
db_name, db_version,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/1);
run_loop.Run();
}
// Open at version < 2, which will fail.
{
const int64_t db_version = 1;
base::RunLoop run_loop;
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
EXPECT_CALL(client, Error)
.WillOnce(::base::test::RunClosure(run_loop.QuitClosure()));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(),
db_name, db_version,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/2);
run_loop.Run();
IndexedDBBucketContext* bucket_context =
GetBucketContext(bucket_locator.id);
ASSERT_TRUE(bucket_context);
EXPECT_FALSE(
base::Contains(bucket_context->GetDatabasesForTesting(), db_name));
}
}
// Test for `IndexedDBDataFormatVersion`.
TEST_P(IndexedDBTest, DataLoss) {
const blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
auto bucket_locator = BucketLocator();
bucket_locator.storage_key = storage_key;
const std::u16string db_name(u"test_db");
// Bind the IDBFactory.
mojo::Remote<blink::mojom::IDBFactory> factory_remote;
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
checker_remote;
BindIndexedDBFactory(std::move(checker_remote),
factory_remote.BindNewPipeAndPassReceiver(),
ToBucketInfo(bucket_locator));
// Set a data format version and create a new database. No data loss.
{
base::AutoReset<IndexedDBDataFormatVersion> override_version(
&IndexedDBDataFormatVersion::GetMutableCurrentForTesting(),
IndexedDBDataFormatVersion(3, 4));
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
base::RunLoop run_loop;
EXPECT_CALL(client, MockedUpgradeNeeded(
_, _, blink::mojom::IDBDataLoss::None, _, _))
.WillOnce(
testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(),
db_name, /*version=*/1,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/1);
run_loop.Run();
// This step is necessary to make sure the backing store is closed so that
// the second `Open` will initialize it with the new (older) data format
// version. Without this step, the same `IndexedDBBackingStore` is reused
// because it's kept around for 2 seconds after the last connection is
// dropped.
base::RunLoop run_loop2;
context_->ForceClose(
bucket_locator.id,
storage::mojom::ForceCloseReason::FORCE_CLOSE_BACKING_STORE_FAILURE,
run_loop2.QuitClosure());
run_loop2.Run();
}
// Set an older data format version and try to reopen said database. Expect
// total data loss.
{
base::AutoReset<IndexedDBDataFormatVersion> override_version(
&IndexedDBDataFormatVersion::GetMutableCurrentForTesting(),
IndexedDBDataFormatVersion(3, 3));
base::RunLoop run_loop;
MockMojoIndexedDBFactoryClient client;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
EXPECT_CALL(client, MockedUpgradeNeeded(
_, _, blink::mojom::IDBDataLoss::Total, _, _))
.WillOnce(
testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
factory_remote->Open(client.CreateInterfacePtrAndBind(),
database_callbacks.CreateInterfacePtrAndBind(),
db_name, /*version=*/1,
transaction_remote.BindNewEndpointAndPassReceiver(),
/*transaction_id=*/2);
run_loop.Run();
}
}
} // namespace content