// 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 <cstdint>
#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/notreached.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/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/run_until.h"
#include "base/test/task_environment.h"
#include "base/test/test_file_util.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_id.h"
#include "components/services/storage/privileged/cpp/bucket_client_info.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.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_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/instance/bucket_context.h"
#include "content/browser/indexed_db/instance/bucket_context_handle.h"
#include "content/browser/indexed_db/instance/connection.h"
#include "content/browser/indexed_db/instance/leveldb/backing_store.h"
#include "content/browser/indexed_db/instance/mock_blob_storage_context.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/browser/indexed_db/status.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 "mojo/public/cpp/test_support/test_utils.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::indexed_db {
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<MockMojoFactoryClient>>()),
        connection_callbacks(
            std::make_unique<StrictMock<MockMojoDatabaseCallbacks>>()) {}

  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, /*priority=*/0);
    // 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<MockMojoFactoryClient> open_callbacks;
  std::unique_ptr<MockMojoDatabaseCallbacks> 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,
                      public testing::WithParamInterface<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()
      : sqlite_override_(BucketContext::OverrideShouldUseSqliteForTesting(
            IsSqliteBackingStoreEnabled())),
        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())) {
    mojo::PendingRemote<storage::mojom::BlobStorageContext>
        pending_blob_storage_context;
    blob_storage_context_.Clone(
        pending_blob_storage_context.InitWithNewPipeAndPassReceiver());
    context_ = std::make_unique<IndexedDBContextImpl>(
        temp_dir_.GetPath(), quota_manager_proxy_.get(),
        std::move(pending_blob_storage_context),
        /*file_system_access_context=*/mojo::NullRemote(),
        base::SequencedTaskRunner::GetCurrentDefault());
    // Let the mojo pipes be bound before proceeding. See
    // IndexedDBContextImpl::BindPipesOnIDBSequence().
    RunPostedTasks();

    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();
  }

  IndexedDBTest(const IndexedDBTest&) = delete;
  IndexedDBTest& operator=(const IndexedDBTest&) = delete;

  ~IndexedDBTest() override = default;

  bool IsSqliteBackingStoreEnabled() { return GetParam(); }

  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() {
    mojo::PendingRemote<storage::mojom::BlobStorageContext>
        pending_blob_storage_context;
    blob_storage_context_.Clone(
        pending_blob_storage_context.InitWithNewPipeAndPassReceiver());
    context_ = std::make_unique<IndexedDBContextImpl>(
        base::FilePath(), quota_manager_proxy_.get(),
        std::move(pending_blob_storage_context),
        /*file_system_access_context=*/mojo::NullRemote(),
        base::SequencedTaskRunner::GetCurrentDefault());
    // The mojo pipes are bound asynchronously, and must be bound before
    // proceeding with testing.
    RunPostedTasks();
  }

  void RunPostedTasks() {
    base::RunLoop loop;
    context_->idb_task_runner()->PostTask(FROM_HERE, loop.QuitClosure());
    loop.Run();
  }

  void SetUp() override { ResetGlobalSweepAndCompactionTimesForTest(); }

  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) {
    return context()->GetFilePathForTesting(bucket_locator,
                                            IsSqliteBackingStoreEnabled());
  }

  bool IsThirdPartyStoragePartitioningEnabled() {
    // Enabled by default since 2023 for most platforms, but still off by
    // default for Android WebView.
    return base::FeatureList::IsEnabled(
        net::features::kThirdPartyStoragePartitioning);
  }

  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 BindFactory(
      mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
          checker_remote,
      mojo::PendingReceiver<blink::mojom::IDBFactory> receiver,
      storage::QuotaErrorOr<storage::BucketInfo> bucket_info) {
    context()->BindIndexedDBImpl(storage::BucketClientInfo{},
                                 std::move(checker_remote), std::move(receiver),
                                 bucket_info);
  }

  blink::StorageKey GetTestStorageKey() {
    return blink::StorageKey::CreateFromStringForTesting("http://test/");
  }

  // 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;
    BindFactory(std::move(checker_remote),
                factory_remote_.BindNewPipeAndPassReceiver(), bucket_info);

    // Open new connection/database, wait for success.
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks 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, 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"opendb",
                          blink::IndexedDBDatabaseMetadata::NO_VERSION,
                          transaction_remote.BindNewEndpointAndPassReceiver(),
                          /*host_transaction_id=*/0, /*priority=*/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();
  }

  BucketContext& GetOrCreateBucketContext(
      const storage::BucketInfo& bucket,
      const base::FilePath& data_directory) {
    context_->EnsureBucketContext(bucket, data_directory);
    return *GetBucketContext(bucket.id);
  }

  BucketContext* GetBucketContext(storage::BucketId id) {
    auto* sequence_bound = context_->GetBucketContextForTesting(id);
    if (!sequence_bound) {
      return nullptr;
    }
    base::test::TestFuture<BucketContext*> future;
    sequence_bound->AsyncCall(&BucketContext::GetReferenceForTesting)
        .Then(future.GetCallback());
    return future.Get();
  }

  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();
  }

  BucketContextHandle CreateBucketHandle(
      std::optional<storage::BucketLocator> bucket_locator = std::nullopt) {
    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));
    BucketContextHandle bucket_context_handle(
        *GetBucketContext(bucket_locator->id));
    bucket_context_handle->InitBackingStore(
        /*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) {
    BucketContext* 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::AutoReset<std::optional<bool>> sqlite_override_;

  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_;
  MockBlobStorageContext blob_storage_context_;
  std::unique_ptr<IndexedDBContextImpl> context_;
  mojo::Remote<blink::mojom::IDBFactory> factory_remote_;
};

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    IndexedDBTest,
    /*use SQLite backing store*/ testing::Bool(),
    [](const testing::TestParamInfo<IndexedDBTest::ParamType>& info) {
      return info.param ? "SQLite" : "LevelDB";
    });

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;
  BindFactory(std::move(checker_remote),
              bounded_factory_remote.BindNewPipeAndPassReceiver(),
              storage::BucketInfo());

  base::RunLoop loop;
  connection = std::make_unique<TestDatabaseConnection>(
      context()->idb_task_runner(), 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)));

  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;
  BindFactory(std::move(checker_remote),
              bounded_factory_remote.BindNewPipeAndPassReceiver(),
              storage::BucketInfo());

  base::RunLoop loop;
  // Open connection.
  connection = std::make_unique<TestDatabaseConnection>(
      context()->idb_task_runner(), 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;
  BindFactory(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()->idb_task_runner(), ToOrigin(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()->idb_task_runner(), 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;
  BindFactory(std::move(checker_remote),
              bounded_factory_remote.BindNewPipeAndPassReceiver(),
              storage::BucketInfo());

  base::RunLoop loop;
  // Open connection.
  connection = std::make_unique<TestDatabaseConnection>(
      context()->idb_task_runner(), 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::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), std::u16string(), 100,
                                     nullptr)));

  auto new_value = blink::mojom::IDBValue::New();
  new_value->bits = mojo_base::BigBuffer(base::as_byte_span("hello"));
  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();
}

// Regression test for crbug.com/461720662. When run under ASAN, the test
// verifies that a Transaction can be destroyed inside `Transaction::RunTasks`
// without causing UAF.
TEST_P(IndexedDBTest, InvalidObjectStoreId) {
  const int64_t kDBVersion = 1;
  const int64_t kTransactionId = 1;
  const int64_t kObjectStoreId = 10;
  const int64_t kIndexId = 100;
  const char16_t kObjectStoreName[] = u"os";
  const char16_t kIndexName[] = u"index";

  // Bind the IDBFactory.
  mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
      checker_remote;
  mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
  BucketContextHandle bucket_context_handle = CreateBucketHandle();
  const BucketLocator& bucket_locator = bucket_context_handle->bucket_locator();
  BindFactory(std::move(checker_remote),
              bounded_factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Open connection.
  std::unique_ptr<TestDatabaseConnection> connection;
  mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
  {
    base::RunLoop loop;
    connection = std::make_unique<TestDatabaseConnection>(
        context()->idb_task_runner(), 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),
                                 QuitLoop(&loop)));

    // Queue open request message.
    connection->Open(bounded_factory_remote.get());
    loop.Run();
  }
  EXPECT_TRUE(pending_database.is_valid());

  {
    mojo::test::BadMessageObserver bad_message_observer;
    // Create object store and index.
    connection->database.Bind(std::move(pending_database));
    ASSERT_TRUE(connection->database.is_bound());
    ASSERT_TRUE(connection->version_change_transaction.is_bound());

    ASSERT_TRUE(connection->database.is_bound());
    connection->version_change_transaction->CreateObjectStore(
        kObjectStoreId, kObjectStoreName, blink::IndexedDBKeyPath(), false);
    connection->database->CreateIndex(
        kTransactionId, kObjectStoreId + 123,
        blink::IndexedDBIndexMetadata(kIndexName, kIndexId,
                                      blink::IndexedDBKeyPath(), false, false));

    EXPECT_EQ("Invalid object_store_id or index_id.",
              bad_message_observer.WaitForBadMessage());
  }
}

TEST_P(IndexedDBTest, 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));

  EXPECT_EQ(0, observer.notify_list_changed_count);
  EXPECT_EQ(0, observer.notify_content_changed_count);

  // Bind the IDBFactory.
  mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
      checker_remote;
  mojo::Remote<blink::mojom::IDBFactory> bounded_factory_remote;
  BucketContextHandle bucket_context_handle = CreateBucketHandle();
  const BucketLocator& bucket_locator = bucket_context_handle->bucket_locator();
  BindFactory(std::move(checker_remote),
              bounded_factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // 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()->idb_task_runner(), 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,
        blink::IndexedDBIndexMetadata(kIndexName, kIndexId,
                                      blink::IndexedDBKeyPath(), false, false));
    connection1->version_change_transaction->Commit(0);

    loop.Run();
  }

  EXPECT_EQ(1, observer.notify_list_changed_count);

  // Connection need to be closed before opening another connection. Because if
  // one connection triggers a version change, it can affect other open
  // connections as well.
  connection1.reset();
  // 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()->idb_task_runner(), 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(&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(2, observer.notify_list_changed_count);

  connection2.reset();

  // 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()->idb_task_runner(), 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(3, observer.notify_list_changed_count);

  // Close the connections to finish the test nicely.
  connection3.reset();
}

MATCHER(IsSuccessKey, "") {
  return arg->is_key();
}

TEST_P(IndexedDBTest, 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;

  BucketContextHandle bucket_context_handle = CreateBucketHandle();
  const BucketLocator& bucket_locator = bucket_context_handle->bucket_locator();
  BindFactory(std::move(checker_remote),
              bounded_factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  base::RunLoop loop;
  // Open connection 1.
  connection1 = std::make_unique<TestDatabaseConnection>(
      context()->idb_task_runner(), 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);

  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);

  auto new_value = blink::mojom::IDBValue::New();
  auto value = base::span_from_cstring("value");
  new_value->bits = mojo_base::BigBuffer(base::as_bytes(value));

  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(1, observer.notify_list_changed_count);
  EXPECT_EQ(1, observer.notify_content_changed_count);

  // Connection need to be closed before opening another connection. Because if
  // one connection triggers a version change, it can affect other open
  // connections as well.
  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()->idb_task_runner(), 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();

  // +1 list changed for the transaction
  EXPECT_EQ(2, 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;
  BindFactory(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()->idb_task_runner(), 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) {
  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));

  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));
}

TEST_P(IndexedDBTest, Bug464999826) {
  quota_manager_->HoldBackResults();

  base::FilePath db_directory =
      GetFilePathForTesting(kNormalFirstPartyBucketLocator);
  ASSERT_TRUE(base::CreateDirectory(db_directory));
  context()->ForceInitializeFromFilesForTesting(base::DoNothing());

  scoped_refptr<base::SequencedTaskRunner> idb_task_runner =
      context_->idb_task_runner();
  IndexedDBContextImpl::Shutdown(std::move(context_));
  base::RunLoop destruction_loop;
  idb_task_runner->PostTask(FROM_HERE, destruction_loop.QuitClosure());
  destruction_loop.Run();

  quota_manager_->ReleaseResults();
}

// Verifies that the IDB connection is force closed and the directory is deleted
// when the bucket is deleted.
TEST_P(IndexedDBTest, 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(IndexedDBTest, ForceCloseOpenDatabasesOnDatabaseError) {
  storage::BucketInfo bucket_info;
  VerifyForcedClosedCalled(
      base::BindOnce(
          [](IndexedDBTest* test, storage::BucketInfo* bucket_info) {
            BucketContext* bucket = test->GetBucketContext(bucket_info->id);
            const auto& dbs = bucket->GetDatabasesForTesting();
            ASSERT_EQ(1U, dbs.size());
            for (auto& [name, db] : dbs) {
              bucket->OnDatabaseError(
                  db.get(), Status::InvalidArgument("operation not supported"),
                  std::string());
            }
          },
          this, &bucket_info),
      &bucket_info);
}

// Verifies that the IDB connection is force closed when the database is deleted
// via the mojo API.
TEST_P(IndexedDBTest, ForceCloseOpenDatabasesOnDeleteDatabase) {
  storage::BucketInfo bucket_info;
  VerifyForcedClosedCalled(
      base::BindOnce(
          [](mojo::Remote<blink::mojom::IDBFactory>* factory_remote) {
            MockMojoFactoryClient 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));
}

// Regression test for https://crbug.com/446722008
TEST_P(IndexedDBTest, AvoidCrashAfterForceCloseDbAndThenOpen) {
  storage::BucketInfo bucket_info = InitBucket(GetTestStorageKey());
  BucketLocator bucket_locator = bucket_info.ToBucketLocator();
  mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker> 
      checker_remote;
  BindFactory(std::move(checker_remote),
              factory_remote_.BindNewPipeAndPassReceiver(), bucket_info);
              
  // Open a database.
  base::RunLoop run_loop_for_first_open;
  MockMojoDatabaseCallbacks database_callbacks;
  EXPECT_CALL(database_callbacks, ForcedClose())
      .WillOnce(
          ::base::test::RunClosure(run_loop_for_first_open.QuitClosure()));
  MockMojoFactoryClient client;
  mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
  EXPECT_CALL(client, MockedOpenSuccess)
      .WillOnce(testing::DoAll(MoveArgPointee<0>(&pending_database)));
  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, /*priority=*/0);
 
  // Delete with force_close = true.
  MockMojoFactoryClient delete_client;
  factory_remote_->DeleteDatabase(delete_client.CreateInterfacePtrAndBind(),
                                  u"opendb",
                                  /*force_close=*/true);
 
  // Open the database again, without waiting for any of the previous steps to
  // finish. The timing of this is very particular, which is why this test does
  // not use `VerifyForcedClosedCalled()`. If the second open() comes any later,
  // it will succeed because the original Database will have finished being
  // deleted. We want to verify that there is no crash in the situation where
  // the second open is handled while the database is still in the process of
  // being deleted.
  MockMojoFactoryClient client2;
  EXPECT_CALL(client2, Error);
  MockMojoDatabaseCallbacks database_callbacks2;
  base::RunLoop run_loop_for_second_open;
  EXPECT_CALL(database_callbacks2, ForcedClose())
      .WillOnce(
          ::base::test::RunClosure(run_loop_for_second_open.QuitClosure()));
  mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote2;
  factory_remote_->Open(
      client2.CreateInterfacePtrAndBind(),
      database_callbacks2.CreateInterfacePtrAndBind(), u"opendb",
      /*version=*/0, transaction_remote2.BindNewEndpointAndPassReceiver(),
      /*host_transaction_id=*/42, /*priority=*/0);
 
  // Block until expectations are satisfied.
  run_loop_for_first_open.Run();
  run_loop_for_second_open.Run();
}

TEST_P(IndexedDBTest, BasicFactoryCreationAndTearDown) {
  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();
  base::FilePath file_1 =
      GetFilePathForTesting(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();
  base::FilePath file_2 =
      GetFilePathForTesting(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();
  base::FilePath file_3 =
      GetFilePathForTesting(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();
  base::FilePath file_4 =
      GetFilePathForTesting(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();
  base::FilePath file_5 =
      GetFilePathForTesting(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))
      .InitBackingStore(true);

  GetOrCreateBucketContext(bucket_2, context()->GetDataPath(bucket_locator_2))
      .InitBackingStore(true);

  GetOrCreateBucketContext(bucket_3, context()->GetDataPath(bucket_locator_3))
      .InitBackingStore(true);

  GetOrCreateBucketContext(bucket_4, context()->GetDataPath(bucket_locator_4))
      .InitBackingStore(true);

  GetOrCreateBucketContext(bucket_5, context()->GetDataPath(bucket_locator_5))
      .InitBackingStore(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) {
  BucketContextHandle 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.
  BucketContextHandle 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(
      storage::BucketClientInfo{}, std::move(checker_remote),
      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.
  BucketContextHandle 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(
      storage::BucketClientInfo{}, std::move(checker_remote),
      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) {
  if (IsSqliteBackingStoreEnabled()) {
    // SQLite doesn't have any pre-close tasks, although it may in the future,
    // such as vacuuming. For now this test is not relevant.
    GTEST_SKIP();
  }

  {
    // Open a connection & immediately release it to cause the closing sequence
    // to start.
    BucketContextHandle 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;
    BindFactory(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(BucketContext::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(kMaxGlobalSweepDelay);

  // 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`.
  BucketContext* bucket_context = nullptr;

  {
    // Open a connection & immediately release it to cause the closing sequence
    // to start again.
    BucketContextHandle 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(BucketContext::ClosingState::kRunningPreCloseTasks,
              bucket_context->closing_stage());
  }

  {
    // Stop sweep by opening a connection.
    BucketContextHandle bucket_context_handle(*bucket_context);
    storage::BucketId bucket_id = bucket_context_handle->bucket_locator().id;
    EXPECT_NE(BucketContext::ClosingState::kRunningPreCloseTasks,
              bucket_context->closing_stage());

    // Move clock forward to trigger next sweep, but storage key has longer
    // sweep minimum, so no tasks should execute.
    task_environment_.FastForwardBy(kMaxGlobalSweepDelay);

    bucket_context_handle.Release();
    EXPECT_EQ(BucketContext::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(kMaxBucketSweepDelay);
    BucketContextHandle 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(BucketContext::ClosingState::kRunningPreCloseTasks,
              bucket_context->closing_stage());
  }
}

TEST_P(IndexedDBTest, InMemoryFactoriesStay) {
  SetUpInMemoryContext();

  BucketContextHandle bucket_context_handle = CreateBucketHandle();
  BucketLocator bucket_locator = bucket_context_handle->bucket_locator();

  EXPECT_TRUE(bucket_context_handle->in_memory());
  BucketContext* 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());
  // 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) {
  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();

  BucketContextHandle bucket_context_handle(GetOrCreateBucketContext(
      ToBucketInfo(bucket_locator), context()->GetDataPath(bucket_locator)));
  Status s;
  std::tie(s, std::ignore, std::ignore) =
      bucket_context_handle->InitBackingStore(
          /*create_if_missing=*/true);

  EXPECT_TRUE(s.IsIOError());
}

TEST_P(IndexedDBTest, FactoryForceClose) {
  BucketContextHandle bucket_context_handle = CreateBucketHandle();
  BucketLocator bucket_locator = bucket_context_handle->bucket_locator();

  bucket_context_handle->ForceClose(
      /*doom=*/false, "The database is force-closed for testing.");
  BucketContext* 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
// BucketContext::Delegate::on_receiver_bounced.
TEST_P(IndexedDBTest, CloseThenAddReceiver) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator bucket_locator = BucketLocator();
  bucket_locator.storage_key = storage_key;

  // Trigger the bucket context to be created.
  mojo::Remote<blink::mojom::IDBFactory> factory_remote1;
  BindFactory(
      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();

  // 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
  // BucketContext::AddReceiver().
  mojo::Remote<blink::mojom::IDBFactory> factory_remote2;
  BindFactory(
      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.
}

// Tests that the backing store is closed when the connection is closed during
// upgrade.
TEST_P(IndexedDBTest, ConnectionCloseDuringUpgrade) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Now create a database and thus the backing store.
  MockMojoFactoryClient client;
  MockMojoDatabaseCallbacks 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, /*priority=*/0);
  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) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Don't create a backing store if one doesn't exist.
  {
    // Delete db.
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    base::RunLoop run_loop;
    EXPECT_CALL(client, DeleteSuccess(0))
        .WillOnce(::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.
  {
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    base::RunLoop run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded)
        .WillOnce(::base::test::RunClosure(run_loop.QuitClosure()));
    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
    factory_remote->Open(client.CreateInterfacePtrAndBind(),
                         database_callbacks.CreateInterfacePtrAndBind(), u"db",
                         blink::IndexedDBDatabaseMetadata::NO_VERSION,
                         transaction_remote.BindNewEndpointAndPassReceiver(),
                         /*transaction_id=*/1, /*priority=*/0);
    run_loop.Run();
  }

  // Delete the database now that the backing store actually exists.
  {
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    base::RunLoop run_loop;
    EXPECT_CALL(client, DeleteSuccess(0))
        .WillOnce(::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());
  }
}

// Verifies that deleting an existing database that is not currently open in the
// backing store works as expected.
TEST_P(IndexedDBTest, DeleteDatabase_Cold) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Create a database with a valid version so that it gets persisted.
  {
    base::HistogramTester histogram_tester;
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
    base::RunLoop upgrade_run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded)
        .WillOnce(testing::DoAll(
            MoveArgPointee<0>(&pending_database),
            ::base::test::RunClosure(upgrade_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, /*priority=*/0);
    upgrade_run_loop.Run();

    // Commit the versionchange transaction, lest it be aborted and rolled back
    // and the database deleted.
    mojo::AssociatedRemote<blink::mojom::IDBDatabase> connection(
        std::move(pending_database));
    transaction_remote->Commit(0);

    base::RunLoop success_run_loop;
    EXPECT_CALL(client, MockedOpenSuccess)
        .WillOnce(::base::test::RunClosure(success_run_loop.QuitClosure()));
    success_run_loop.Run();

    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CreateIfMissing.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CreateOrOpenDatabase.OnDisk",
        0 /*Status::Type::kOk*/, 1);
  }

  // Fast forward by the grace period so that the backing store gets closed.
  task_environment_.FastForwardBy(base::Seconds(2));
  VerifyBucketContext(bucket_locator.id, /*expected_context_exists=*/true,
                      /*expected_backing_store_exists=*/false);

  // Delete the database now, which should require reopening the backing store
  // (and the database).
  {
    base::HistogramTester histogram_tester;
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    base::RunLoop run_loop;
    EXPECT_CALL(client, DeleteSuccess(1))
        .WillOnce(::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();

    // The backing store itself should not be created, just opened.
    histogram_tester.ExpectTotalCount(
        "IndexedDB.BackingStore.CreateIfMissing.OnDisk", 0);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CreateOrOpenDatabase.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.DeleteDatabase.OnDisk", 0 /*Status::Type::kOk*/,
        1);
  }
}

// Verifies the behavior when several delete requests for the same database are
// queued together.
TEST_P(IndexedDBTest, DeleteDatabase_DuplicateRequests) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Open (create) a database.
  mojo::AssociatedRemote<blink::mojom::IDBDatabase> connection;
  {
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
    base::RunLoop upgrade_run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded)
        .WillOnce(testing::DoAll(
            MoveArgPointee<0>(&pending_database),
            ::base::test::RunClosure(upgrade_run_loop.QuitClosure())));
    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
    factory_remote->Open(
        client.CreateInterfacePtrAndBind(),
        database_callbacks.CreateInterfacePtrAndBind(), kDatabaseName,
        /*version=*/1, transaction_remote.BindNewEndpointAndPassReceiver(),
        /*transaction_id=*/1, /*priority=*/0);
    upgrade_run_loop.Run();

    // Commit the versionchange transaction so the database gets persisted.
    connection.Bind(std::move(pending_database));
    transaction_remote->Commit(0);
    EXPECT_CALL(database_callbacks, Complete);

    base::RunLoop success_run_loop;
    EXPECT_CALL(client, MockedOpenSuccess)
        .WillOnce(::base::test::RunClosure(success_run_loop.QuitClosure()));
    success_run_loop.Run();
  }

  // Issue two delete requests in succession. The first one should really delete
  // the database, while the second should find the database non-existent.
  base::HistogramTester histogram_tester;
  base::RunLoop run_loop;

  MockMojoFactoryClient first_client;
  EXPECT_CALL(first_client, DeleteSuccess(1));
  MockMojoFactoryClient second_client;
  EXPECT_CALL(second_client, DeleteSuccess(0))
      .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));

  factory_remote->DeleteDatabase(first_client.CreateInterfacePtrAndBind(),
                                 kDatabaseName,
                                 /*force_close=*/false);
  factory_remote->DeleteDatabase(second_client.CreateInterfacePtrAndBind(),
                                 kDatabaseName,
                                 /*force_close=*/false);
  connection.reset();
  run_loop.Run();

  // The first delete request should find the database already open, and the
  // second one should not attempt to create it.
  histogram_tester.ExpectTotalCount(
      "IndexedDB.BackingStore.CreateOrOpenDatabase.OnDisk", 0);
  // Only the first request should call into the backing store.
  histogram_tester.ExpectUniqueSample(
      "IndexedDB.BackingStore.DeleteDatabase.OnDisk", 0 /*Status::Type::kOk*/,
      1);
}

TEST_P(IndexedDBTest, GetDatabaseNames_NoFactory) {
  base::HistogramTester histogram_tester;
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(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());
    // The duration histogram should not be recorded since this was a trivial
    // request (the backing store was not involved).
    histogram_tester.ExpectTotalCount(
        "IndexedDB.IDBFactory.GetDatabaseInfo.Duration.OnDisk", 0);
  }

  // Now create a database and thus the backing store.
  MockMojoFactoryClient client;
  MockMojoDatabaseCallbacks 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, 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",
                       blink::IndexedDBDatabaseMetadata::NO_VERSION,
                       transaction_remote.BindNewEndpointAndPassReceiver(),
                       /*transaction_id=*/1, /*priority=*/0);
  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());

    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.GetDatabaseNamesAndVersions.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectTotalCount(
        "IndexedDB.IDBFactory.GetDatabaseInfo.Duration.OnDisk", 1);
  }
}

// Regression test for crbug.com/376461709
TEST_P(IndexedDBTest, UpdatePriorityAfterForceClose) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Bind a connection/database.
  MockMojoFactoryClient client;
  MockMojoDatabaseCallbacks 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",
                       blink::IndexedDBDatabaseMetadata::NO_VERSION,
                       transaction_remote.BindNewEndpointAndPassReceiver(),
                       /*transaction_id=*/1, /*priority=*/0);
  run_loop.Run();

  mojo::AssociatedRemote<blink::mojom::IDBDatabase> connection(
      std::move(pending_database));
  // Simulate force closing the context while `UpdatePriority` is in flight.
  context_->ForceClose(bucket_locator.id, {}, base::DoNothing());
  // Call this second in the unit test context to simulate losing the race.
  connection->UpdatePriority(1);
  connection.FlushForTesting();

  // Not crashing indicates success.
}

TEST_P(IndexedDBTest, TransactionHistograms) {
  constexpr int64_t kObjectStoreId = 1;
  int64_t transaction_id = 0;

  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Create a database with a valid version so that a version change transaction
  // is created.
  MockMojoFactoryClient client;
  MockMojoDatabaseCallbacks database_callbacks;
  mojo::AssociatedRemote<blink::mojom::IDBDatabase> connection;
  {
    base::HistogramTester histogram_tester;
    mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
    base::RunLoop upgrade_run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded)
        .WillOnce(testing::DoAll(
            MoveArgPointee<0>(&pending_database),
            ::base::test::RunClosure(upgrade_run_loop.QuitClosure())));
    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction;
    factory_remote->Open(client.CreateInterfacePtrAndBind(),
                         database_callbacks.CreateInterfacePtrAndBind(), u"db",
                         /*version=*/1,
                         transaction.BindNewEndpointAndPassReceiver(),
                         ++transaction_id, /*priority=*/0);
    upgrade_run_loop.Run();
    connection.Bind(std::move(pending_database));

    // Create an object store and commit the version change transaction.
    transaction->CreateObjectStore(kObjectStoreId, u"store",
                                   blink::IndexedDBKeyPath(),
                                   /*auto_increment=*/true);
    transaction->Commit(0);

    // Wait for the transaction to complete.
    base::RunLoop loop;
    EXPECT_CALL(database_callbacks, Complete(transaction_id))
        .WillOnce(base::test::RunClosure(loop.QuitClosure()));
    loop.Run();
    EXPECT_CALL(client, MockedOpenSuccess);

    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.BeginTransaction.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.ChangeDatabaseVersion.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CreateObjectStore.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CommitPhaseOne.OnDisk", 0 /*Status::Type::kOk*/,
        1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CommitPhaseTwo.OnDisk", 0 /*Status::Type::kOk*/,
        1);
  }

  // Create a transaction and commit it without issuing any request.
  {
    base::HistogramTester histogram_tester;
    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction;
    connection->CreateTransaction(
        transaction.BindNewEndpointAndPassReceiver(), ++transaction_id,
        {kObjectStoreId}, blink::mojom::IDBTransactionMode::ReadWrite,
        blink::mojom::IDBTransactionDurability::Relaxed);
    transaction->Commit(0);

    // Wait for the transaction to complete.
    base::RunLoop loop;
    EXPECT_CALL(database_callbacks, Complete(transaction_id))
        .WillOnce(base::test::RunClosure(loop.QuitClosure()));
    loop.Run();

    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.BeginTransaction.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    // The commit does not propagate to the BackingStore since no requests were
    // issued to the transaction.
    histogram_tester.ExpectTotalCount(
        "IndexedDB.BackingStore.CommitPhaseOne.OnDisk", 0);
    histogram_tester.ExpectTotalCount(
        "IndexedDB.BackingStore.CommitPhaseTwo.OnDisk", 0);
  }

  // Create another transaction and issue some requests.
  {
    base::HistogramTester histogram_tester;
    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction;
    connection->CreateTransaction(
        transaction.BindNewEndpointAndPassReceiver(), ++transaction_id,
        {kObjectStoreId}, blink::mojom::IDBTransactionMode::ReadWrite,
        blink::mojom::IDBTransactionDurability::Relaxed);

    transaction->Put(kObjectStoreId,
                     blink::mojom::IDBValuePtr(blink::mojom::IDBValue::New()),
                     blink::IndexedDBKey(), blink::mojom::IDBPutMode::AddOnly,
                     /*index_keys=*/{},
                     base::BindLambdaForTesting(
                         [&](blink::mojom::IDBTransactionPutResultPtr result) {
                           EXPECT_FALSE(result->is_error_result());
                         }));
    transaction->Commit(0);

    // Wait for the transaction to complete.
    base::RunLoop loop;
    EXPECT_CALL(database_callbacks, Complete(transaction_id))
        .WillOnce(base::test::RunClosure(loop.QuitClosure()));
    loop.Run();

    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.BeginTransaction.OnDisk",
        0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.PutRecord.OnDisk", 0 /*Status::Type::kOk*/, 1);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CommitPhaseOne.OnDisk", 0 /*Status::Type::kOk*/,
        1);
    histogram_tester.ExpectTotalCount(
        "IndexedDB.BackingStore.WriteBlobs.OnDisk", 0);
    histogram_tester.ExpectUniqueSample(
        "IndexedDB.BackingStore.CommitPhaseTwo.OnDisk", 0 /*Status::Type::kOk*/,
        1);
  }
}

TEST_P(IndexedDBTest, QuotaErrorOnDbOpenError) {
  base::HistogramTester histograms;
  if (IsSqliteBackingStoreEnabled()) {
    // The mechanism used to induce errors (`MakeFileUnwritable`) doesn't work
    // on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
    GTEST_SKIP();
#endif  // BUILDFLAG(IS_FUCHSIA)
  } else {
    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");
  BucketLocator bucket_locator = BucketLocator();
  bucket_locator.is_default = true;
  bucket_locator.storage_key = storage_key;
  mojo::Remote<blink::mojom::IDBFactory> factory_remote;
  mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
      checker_remote;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  std::optional<base::FilePermissionRestorer> permission_restorer;
  if (IsSqliteBackingStoreEnabled()) {
    // First create a database successfully so that the directory exists, then
    // make the directory unwritable. This will make future attempts to open the
    // file fail.
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    base::RunLoop run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded)
        .WillOnce(::base::test::RunClosure(run_loop.QuitClosure()));
    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
    factory_remote->Open(client.CreateInterfacePtrAndBind(),
                         database_callbacks.CreateInterfacePtrAndBind(), u"db2",
                         blink::IndexedDBDatabaseMetadata::NO_VERSION,
                         transaction_remote.BindNewEndpointAndPassReceiver(),
                         /*transaction_id=*/1, /*priority=*/0);
    run_loop.Run();

    base::FilePath data_path = GetFilePathForTesting(bucket_locator);
    permission_restorer.emplace(data_path);
    ASSERT_TRUE(base::MakeFileUnwritable(data_path))
        << base::File::GetLastFileError();
    histograms.ExpectTotalCount("IndexedDB.SQLite.OpenRetryResult", 0);
  }

  // Expect an error when opening.
  MockMojoFactoryClient client;
  MockMojoDatabaseCallbacks database_callbacks;
  base::RunLoop run_loop;
  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(), u"db",
                       /*version=*/1,
                       transaction_remote.BindNewEndpointAndPassReceiver(),
                       /*transaction_id=*/2, /*priority=*/0);
  run_loop.Run();

  if (IsSqliteBackingStoreEnabled()) {
    histograms.ExpectUniqueSample("IndexedDB.SQLite.OpenRetryResult",
                                  5 /*Status::Type::kDatabaseEngine*/, 1);
  }

  // An error on open results in a write 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) {
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator 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;
  BindFactory(std::move(checker_remote),
              factory_remote.BindNewPipeAndPassReceiver(),
              ToBucketInfo(bucket_locator));

  // Open at version 2.
  {
    const int64_t db_version = 2;
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    base::RunLoop run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded)
        .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=*/1, /*priority=*/0);
    run_loop.Run();
  }

  // Open at version < 2, which will fail.
  {
    const int64_t db_version = 1;
    base::RunLoop run_loop;
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks 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, /*priority=*/0);
    run_loop.Run();
    BucketContext* 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");
  BucketLocator 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;
  BindFactory(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));
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    mojo::PendingAssociatedRemote<blink::mojom::IDBDatabase> pending_database;
    base::RunLoop run_loop;
    EXPECT_CALL(client, MockedUpgradeNeeded(
                            _, _, blink::mojom::IDBDataLoss::None, _, _))
        .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(),
                         db_name, /*version=*/1,
                         transaction_remote.BindNewEndpointAndPassReceiver(),
                         /*transaction_id=*/1, /*priority=*/0);
    run_loop.Run();

    // Commit the versionchange transaction, lest it be aborted and rolled back
    // and the database deleted.
    mojo::AssociatedRemote<blink::mojom::IDBDatabase> connection(
        std::move(pending_database));
    transaction_remote->Commit(0);
    transaction_remote.FlushForTesting();

    // 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 `BackingStore` 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::HistogramTester histograms;
    base::AutoReset<IndexedDBDataFormatVersion> override_version(
        &IndexedDBDataFormatVersion::GetMutableCurrentForTesting(),
        IndexedDBDataFormatVersion(3, 3));
    base::RunLoop run_loop;
    MockMojoFactoryClient client;
    MockMojoDatabaseCallbacks database_callbacks;
    EXPECT_CALL(client, MockedUpgradeNeeded(
                            _, _, blink::mojom::IDBDataLoss::Total, _, _))
        .WillOnce(::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, /*priority=*/0);
    run_loop.Run();
    if (IsSqliteBackingStoreEnabled()) {
      histograms.ExpectUniqueSample("IndexedDB.SQLite.OpenRetryResult",
                                    0 /*Status::Type::kOk*/, 1);
    }
  }
}

#if BUILDFLAG(IS_WIN)
TEST_P(IndexedDBTest, FilePathLengthLogging) {
  base::HistogramTester histograms;

  // Open with a normal length origin; success.
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting("http://localhost:81");
  BucketLocator bucket_locator = BucketLocator();
  bucket_locator.storage_key = storage_key;

  {
    mojo::Remote<blink::mojom::IDBFactory> factory_remote;
    mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
        checker_remote;
    BindFactory(std::move(checker_remote),
                factory_remote.BindNewPipeAndPassReceiver(),
                ToBucketInfo(bucket_locator));

    {
      const int64_t db_version = 1;
      MockMojoFactoryClient client;
      MockMojoDatabaseCallbacks database_callbacks;
      base::RunLoop run_loop;
      EXPECT_CALL(client, MockedUpgradeNeeded)
          .WillOnce(::base::test::RunClosure(run_loop.QuitClosure()));
      mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
      factory_remote->Open(client.CreateInterfacePtrAndBind(),
                           database_callbacks.CreateInterfacePtrAndBind(),
                           /*db_name=*/u"db", db_version,
                           transaction_remote.BindNewEndpointAndPassReceiver(),
                           /*transaction_id=*/1, /*priority=*/0);
      run_loop.Run();
    }
  }

  if (IsSqliteBackingStoreEnabled()) {
    histograms.ExpectTotalCount("IndexedDB.FilePathLengthOverflow.LevelDB", 0);
  } else {
    // Normal origin: no path length issues; underflow buckets.
    histograms.ExpectUniqueSample("IndexedDB.FilePathLengthOverflow.LevelDB", 0,
                                  1);
    histograms.ExpectUniqueSample("IndexedDB.FilePathLengthOverflow.SQLite", 0,
                                  1);
  }

  // Open with a super long origin; error.
  bucket_locator.storage_key = blink::StorageKey::CreateFromStringForTesting(
      std::string("https://") + std::string(230, 'a') + ".com:81");
  bucket_locator.id = storage::BucketId::FromUnsafeValue(2);
  {
    mojo::Remote<blink::mojom::IDBFactory> factory_remote;
    mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
        checker_remote;
    BindFactory(std::move(checker_remote),
                factory_remote.BindNewPipeAndPassReceiver(),
                ToBucketInfo(bucket_locator));

    {
      const int64_t db_version = 1;
      MockMojoFactoryClient client;
      MockMojoDatabaseCallbacks database_callbacks;
      base::RunLoop run_loop;
      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=*/u"db", db_version,
                           transaction_remote.BindNewEndpointAndPassReceiver(),
                           /*transaction_id=*/1, /*priority=*/0);
      run_loop.Run();
    }
  }

  if (IsSqliteBackingStoreEnabled()) {
    histograms.ExpectTotalCount("IndexedDB.FilePathLengthOverflow.LevelDB", 0);
  } else {
    // Expect additional logs to both of the histograms. Note that the exact
    // bucket depends on the length of the temp dir.
    histograms.ExpectTotalCount("IndexedDB.FilePathLengthOverflow.LevelDB", 2);
    histograms.ExpectTotalCount("IndexedDB.FilePathLengthOverflow.SQLite", 2);

    // The longest SQLite file name overflows by more than the LevelDB
    // equivalent.
    EXPECT_LT(
        histograms.GetTotalSum("IndexedDB.FilePathLengthOverflow.LevelDB"),
        histograms.GetTotalSum("IndexedDB.FilePathLengthOverflow.SQLite"));
  }
}
#endif

}  // namespace content::indexed_db