910e62b5创建于 1月15日历史提交
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_INDEXED_DB_INSTANCE_BUCKET_CONTEXT_H_
#define CONTENT_BROWSER_INDEXED_DB_INSTANCE_BUCKET_CONTEXT_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <queue>
#include <string>

#include "base/auto_reset.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/memory_dump_provider.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.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_test.mojom.h"
#include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom.h"
#include "components/services/storage/public/cpp/buckets/bucket_info.h"
#include "components/services/storage/public/cpp/quota_error_or.h"
#include "components/services/storage/public/mojom/blob_storage_context.mojom.h"
#include "components/services/storage/public/mojom/file_system_access_context.mojom.h"
#include "content/browser/indexed_db/blob_reader.h"
#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
#include "content/browser/indexed_db/indexed_db_database_error.h"
#include "content/browser/indexed_db/indexed_db_external_object.h"
#include "content/browser/indexed_db/status.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"

namespace storage {
class QuotaManagerProxy;
}

namespace content::indexed_db {

class BackingStore;
class BucketContextHandle;
class Database;

// BucketContext manages the per-bucket IndexedDB state, and other important
// context like the backing store and lock manager.
//
// BucketContext will keep its backing store around while any of these is true:
// * There are handles referencing the bucket context,
// * There are outstanding blob references to this database's blob files, or
// * The bucket context is in-memory (i.e. an incognito profile).
//
// When these qualities are no longer true, `RunTasks()` will invoke
// `ResetBackingStore()`, which returns `this` to an uninitialized state.
//
// Each instance of this class is created and run on a unique thread pool
// SequencedTaskRunner, i.e. the "bucket thread".
class CONTENT_EXPORT BucketContext
    : public blink::mojom::IDBFactory,
      public base::trace_event::MemoryDumpProvider {
 public:
  using DBMap = base::flat_map<std::u16string, std::unique_ptr<Database>>;

  // `CheckCanUseDiskSpace` fudges quota values a little. If there is excess
  // free space, QuotaManager may not be checked the next time a transaction
  // requests space. The decays over this time period.
  static constexpr const base::TimeDelta kBucketSpaceCacheTimeLimit =
      base::Seconds(30);

  enum class ClosingState {
    // BucketContext isn't closing.
    kNotClosing,
    // BucketContext is pausing for kBackingStoreGracePeriodSeconds
    // to allow new references to open before closing the backing store.
    kPreCloseGracePeriod,
    // The `pre_close_task_queue` is running any pre-close tasks.
    kRunningPreCloseTasks,
    kClosed,
  };

  // This structure defines the interface between `BucketContext` and the
  // broader context that exists per Storage Partition (i.e. BrowserContext).
  // TODO(crbug.com/40279485): for now these callbacks execute on the current
  // sequence, but in the future they should be bound to the main IDB sequence.
  struct CONTENT_EXPORT Delegate {
    Delegate();
    Delegate(Delegate&&);
    ~Delegate();

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

    // Called when the bucket context is ready to be destroyed. After this is
    // called, the bucket context will no longer accept new IDBFactory
    // connections (receivers in `receivers_`).
    base::OnceCallback<void()> on_ready_for_destruction;

    // Called when `BucketContext` can't handle an `AddReceiver()` call:
    // specifically, if destruction has already been initiated by calling
    // `on_ready_for_destruction`.
    base::RepeatingCallback<void(
        const storage::BucketClientInfo& /*client_info*/,
        mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
        /*client_state_checker_remote*/,
        mojo::PendingReceiver<blink::mojom::IDBFactory> /*pending_receiver*/)>
        on_receiver_bounced;

    // Called when database content has changed. Technically this is called when
    // the content *probably will* change --- it's invoked before a transaction
    // is actually committed --- but it's only used for devtools so accuracy
    // isn't that important.
    base::RepeatingCallback<void(const std::u16string& /*database_name*/,
                                 const std::u16string& /*object_store_name*/)>
        on_content_changed;

    // Called to inform the quota system that an action which may have updated
    // the amount of disk space used has completed. The parameter is true for
    // transactions that caused the backing store to flush.
    base::RepeatingCallback<void(bool /*did_sync*/)> on_files_written;
  };

  BucketContext(storage::BucketInfo bucket_info,
                const base::FilePath& data_path,
                Delegate&& delegate,
                scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
                mojo::PendingRemote<storage::mojom::BlobStorageContext>
                    blob_storage_context,
                mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
                    file_system_access_context);

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

  ~BucketContext() override;

  // All `BucketContext` instances created during the lifetime of the returned
  // object will use SQLite iff `use_sqlite` is true.
  static base::AutoReset<std::optional<bool>> OverrideShouldUseSqliteForTesting(
      bool use_sqlite);

  bool ShouldUseSqlite() const { return should_use_sqlite_; }

  void QueueRunTasks();

  // Returns true if a RunTask invocation is queued. To be used by metrics.
  bool task_run_queued() const { return task_run_queued_; }

  // Closes the bucket context, i.e. closes the backing store and closes Mojo
  // connections to renderers. When `doom` is true, the directories containing
  // data will also be deleted. Normally, in-memory bucket contexts never close.
  // If this is called with `doom` set to true, they will close.
  void ForceClose(bool doom, const std::string& message);

  // Starts capturing state data for indexeddb-internals. The data will be
  // returned the next time `StopMetadataRecording()` is invoked.
  void StartMetadataRecording();
  std::vector<storage::mojom::IdbBucketMetadataPtr> StopMetadataRecording();

  int64_t GetInMemorySize();

  bool IsClosing() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return closing_stage_ != ClosingState::kNotClosing;
  }

  ClosingState closing_stage() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return closing_stage_;
  }

  void ReportOutstandingBlobs(bool blobs_outstanding);

  // Called when `space_requested` bytes are about to be used by committing a
  // transaction. Will invoke `disk_space_check_callback` if this usage is
  // approved, or false if there's insufficient space as per the `QuotaManager`.
  // If `disk-space_check_callback` is non-null, it will be invoked with the
  // response. If it is null, the check is considered a dry-run, which warms up
  // the space cache but doesn't decrement from it.
  void CheckCanUseDiskSpace(
      int64_t space_requested,
      base::OnceCallback<void(bool)> disk_space_check_callback);

  // Create external objects from |objects| and store the results in
  // |mojo_objects|. |mojo_objects| must be the same length as |objects|.
  // Only used for LevelDB.
  void CreateAllExternalObjects(
      const std::vector<IndexedDBExternalObject>& objects,
      std::vector<blink::mojom::IDBExternalObjectPtr>* mojo_objects);

  const storage::BucketInfo& bucket_info() { return bucket_info_; }
  storage::BucketLocator bucket_locator() {
    return bucket_info_.ToBucketLocator();
  }
  BackingStore* backing_store() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return backing_store_.get();
  }
  const DBMap& GetDatabasesForTesting() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return databases_;
  }
  PartitionedLockManager& lock_manager() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return *lock_manager_;
  }
  const PartitionedLockManager& lock_manager() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return *lock_manager_;
  }

  Delegate& delegate() { return delegate_; }

  base::OneShotTimer* close_timer() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return &close_timer_;
  }

  base::WeakPtr<BucketContext> AsWeakPtr() {
    return weak_factory_.GetWeakPtr();
  }

  storage::QuotaManagerProxy* quota_manager() {
    return quota_manager_proxy_.get();
  }

  storage::mojom::BlobStorageContext* blob_storage_context() {
    return blob_storage_context_.get();
  }
  storage::mojom::FileSystemAccessContext* file_system_access_context() {
    return file_system_access_context_.get();
  }

  void AddReceiver(
      const storage::BucketClientInfo& client_info,
      mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
          client_state_checker_remote,
      mojo::PendingReceiver<blink::mojom::IDBFactory> pending_receiver);

  // blink::mojom::IDBFactory implementation:
  void GetDatabaseInfo(GetDatabaseInfoCallback callback) override;
  void Open(mojo::PendingAssociatedRemote<blink::mojom::IDBFactoryClient>
                factory_client,
            mojo::PendingAssociatedRemote<blink::mojom::IDBDatabaseCallbacks>
                database_callbacks_remote,
            const std::u16string& name,
            int64_t version,
            mojo::PendingAssociatedReceiver<blink::mojom::IDBTransaction>
                transaction_receiver,
            int64_t transaction_id,
            int scheduling_priority) override;
  void DeleteDatabase(mojo::PendingAssociatedRemote<
                          blink::mojom::IDBFactoryClient> factory_client,
                      const std::u16string& name,
                      bool force_close) override;

  // Finishes filling in `info` with data relevant to idb-internals and passes
  // the result back via the return value.
  storage::mojom::IdbBucketMetadataPtr FillInMetadata(
      storage::mojom::IdbBucketMetadataPtr info);
  // Called when the data used to populate the struct in `FillInMetadata` is
  // changed in a significant way.
  void NotifyOfIdbInternalsRelevantChange();

  // This exists to facilitate unit tests. Since `this` is owned via a
  // `SequenceBound`, it's not possible to directly grab pointer to `this`.
  BucketContext* GetReferenceForTesting();

  void FlushBackingStoreForTesting();
  void BindMockFailureSingletonForTesting(
      mojo::PendingReceiver<storage::mojom::MockFailureInjector> receiver);

  // Called when a fatal error has occurred that should result in tearing down
  // the backing store. `BucketContext` *may* be synchronously destroyed after
  // this is invoked. The string, if non-empty, is used as an error message.
  // `database` is used in SQLite mode only.
  void OnDatabaseError(Database* database,
                       Status status,
                       const std::string& message);

  // Called when the backing store has been corrupted.
  void HandleBackingStoreCorruption(const std::string& error_message);

  // base::trace_event::MemoryDumpProvider:
  bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
                    base::trace_event::ProcessMemoryDump* pmd) override;

  bool in_memory() const { return data_path_.empty(); }

 private:
  friend BucketContextHandle;
  friend class BackingStoreTestBase;
  friend class DatabaseTest;
  friend class IndexedDBTest;
  friend class TransactionTestBase;

  FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, CompactionKillSwitchWorks);
  FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, TooLongOrigin);
  FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, BasicFactoryCreationAndTearDown);
  FRIEND_TEST_ALL_PREFIXES(BucketContextTest, BucketSpaceDecay);
  FRIEND_TEST_ALL_PREFIXES(BucketContextTest, MetadataRecordingStateHistory);

  // The data structure that stores everything bound to the receiver. This will
  // be stored together with the receiver in the `mojo::ReceiverSet`.
  struct ReceiverContext {
    ReceiverContext(
        const storage::BucketClientInfo& client_info,
        mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
            client_state_checker_remote);

    ~ReceiverContext();

    ReceiverContext(const ReceiverContext&) = delete;
    ReceiverContext(ReceiverContext&&) noexcept;
    ReceiverContext& operator=(const ReceiverContext&) = delete;
    ReceiverContext& operator=(ReceiverContext&&) = delete;

    const storage::BucketClientInfo client_info;
    mojo::Remote<storage::mojom::IndexedDBClientStateChecker>
        client_state_checker_remote;
  };

  Database* CreateAndAddDatabase(const std::u16string& name);

  void OnHandleCreated();
  void OnHandleDestruction();

  // Returns true if this bucket context can be closed (no references, no blobs,
  // and not persisting for incognito).
  bool CanClose();

  void MaybeStartClosing();
  void StartClosing();
  void CloseNow();
  void StartPreCloseTasks();

  void RunTasks();

  void OnGotBucketSpaceRemaining(storage::QuotaErrorOr<int64_t> space_left);

  // Returns the amount of bucket space `this` has the authority to approve by
  // decaying `bucket_space_remaining_` according to the amount of time passed
  // since `bucket_space_remaining_timestamp_`.
  int64_t GetBucketSpaceToAllot();

  // Hooks up a `BlobReader` to `receiver` for the blob described by
  // `blob_info`.
  void BindBlobReader(const IndexedDBExternalObject& blob_info,
                      mojo::PendingReceiver<blink::mojom::Blob> receiver);
  // Removes all readers for this file path.
  void RemoveBoundReaders(const base::FilePath& path);

  std::tuple<Status, DatabaseError, IndexedDBDataLossInfo> InitBackingStore(
      bool create_if_missing);

  // Destroys `backing_store_` and all associated state. If there are no
  // receivers remaining, it will also destroy `this`.
  void ResetBackingStore();

  // Called when a receiver from `receiver_set_` has been disconnected. If there
  // are no receivers left and the backing store is already destroyed, this will
  // initiate destruction of `this`.
  void OnReceiverDisconnected();

  // Records one tick of Metadata during a metadata recording session.
  void RecordInternalsSnapshot();

  std::string SanitizeErrorMessage(const std::string& message);

  SEQUENCE_CHECKER(sequence_checker_);

  const storage::BucketInfo bucket_info_;

  // Base directory for blobs and backing store files.
  const base::FilePath data_path_;

  // True if the backing store is SQLite, or would be SQLite if it existed.
  bool should_use_sqlite_ = false;

  // True if there are blobs referencing this backing store that are still
  // alive. This is used as closing criteria for this object, see CanClose.
  bool has_blobs_outstanding_ = false;
  bool skip_closing_sequence_ = false;

  bool running_tasks_ = false;

  ClosingState closing_stage_ = ClosingState::kNotClosing;
  base::OneShotTimer close_timer_;
  std::unique_ptr<PartitionedLockManager> lock_manager_;
  std::unique_ptr<BackingStore> backing_store_;
  scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;

  // Databases in the backing store which are already loaded/represented by
  // Database objects. The backing store may have other databases which
  // have not yet been loaded.
  uint32_t next_database_id_for_locks_ = 0;
  DBMap databases_;
  // This is the refcount for the number of BucketContextHandle's given out for
  // this bucket context using OpenReference. This is used as closing criteria
  // for this object, see CanClose.
  int64_t open_handles_ = 0;

  // A queue of callbacks representing `CheckCanUseDiskSpace()` requests.
  std::queue<std::tuple<int64_t /*space_requested*/,
                        base::OnceCallback<void(bool /*allowed*/)>>>
      bucket_space_check_callbacks_;
  // The number of bytes `this` has the authority to approve in response to
  // `CheckCanUseDiskSpace` requests before the QuotaManager will be consulted
  // once more. This is a performance optimization.
  int64_t bucket_space_remaining_ = 0;
  // Timestamp when `bucket_space_remaining_` was last fetched from the quota
  // manager.
  base::TimeTicks bucket_space_remaining_timestamp_;

  // Members in the following block are used for `CreateAllExternalObjects`.
  // Mojo connection to `BlobStorageContext`, which runs on the IO thread.
  mojo::Remote<storage::mojom::BlobStorageContext> blob_storage_context_;
  // Mojo connection to `FileSystemAccessContextImpl`, which runs on the UI
  // thread.
  mojo::Remote<storage::mojom::FileSystemAccessContext>
      file_system_access_context_;
  // This map's value type contains a closure which will run on destruction.
  std::map<base::FilePath,
           std::tuple<std::unique_ptr<BlobReader>,
                      base::ScopedClosureRunner /*release_callback*/>>
      file_reader_map_;

  Delegate delegate_;

  // In-memory contexts will not self-close until this bit is flipped to true.
  bool is_doomed_ = false;

  // True if there's already a task queued to call `RunTasks()`.
  bool task_run_queued_ = false;

  std::vector<storage::mojom::IdbBucketMetadataPtr> metadata_recording_buffer_;
  bool metadata_recording_enabled_ = false;
  base::Time metadata_recording_start_time_;

  mojo::ReceiverSet<blink::mojom::IDBFactory, ReceiverContext> receivers_;

  base::WeakPtrFactory<BucketContext> weak_factory_{this};
};

}  // namespace content::indexed_db

#endif  // CONTENT_BROWSER_INDEXED_DB_INSTANCE_BUCKET_CONTEXT_H_