// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PARKABLE_IMAGE_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PARKABLE_IMAGE_H_

#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/renderer/platform/disk_data_metadata.h"
#include "third_party/blink/renderer/platform/image-decoders/rw_buffer.h"
#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"

namespace blink {

class SegmentReader;
class ParkableImageManager;
class ParkableImage;
class ParkableImageSegmentReader;

PLATFORM_EXPORT BASE_DECLARE_FEATURE(kDelayParkingImages);

// Implementation of ParkableImage. See ParkableImage below.
// We split ParkableImage like this because we want to avoid destroying the
// content of the ParkableImage on anything besides the main thread.
// See |ParkableImageManager::MaybeParkImages| for details on this.
class PLATFORM_EXPORT ParkableImageImpl final
    : public ThreadSafeRefCounted<ParkableImageImpl> {
 public:
  ParkableImageImpl& operator=(const ParkableImage&) = delete;
  ParkableImageImpl(const ParkableImage&) = delete;

  // Smallest encoded size that will actually be parked.
  static constexpr size_t kMinSizeToPark = 1024;  // 1 KiB
  // How long to wait before parking an image.
  //
  // Chosen arbitrarily, did not regress metrics in field trials in 2022. From
  // local experiments, images are typically only decoded once, to raster the
  // tile(s) they are a part of, then never used as long as the image decode
  // cache is not emptied and the tiles are not re-rasterized. This is set to
  // something longer than e.g. 1s in case there is a looping GIF for instance,
  // and/or the decoded image cache is too small.
  static constexpr base::TimeDelta kParkingDelay = base::Seconds(30);

 private:
  friend class ThreadSafeRefCounted<ParkableImageImpl>;
  template <typename T, typename... Args>
  friend scoped_refptr<T> base::MakeRefCounted(Args&&... args);
  friend class ParkableImageManager;
  friend class ParkableImage;
  friend class ParkableImageBaseTest;
  friend class ParkableImageSegmentReader;

  // |initial_capacity| reserves space in the internal buffer, if you know how
  // much data you'll be appending in advance.
  explicit ParkableImageImpl(size_t initial_capacity = 0);

  ~ParkableImageImpl();

  // Factory method to construct a ParkableImageImpl.
  static scoped_refptr<ParkableImageImpl> Create(size_t initial_capacity = 0);

  // Implementations of the methods of the same name from ParkableImage.
  void Freeze() LOCKS_EXCLUDED(lock_);
  void Append(SharedBuffer* buffer, size_t offset = 0) LOCKS_EXCLUDED(lock_);
  scoped_refptr<SharedBuffer> Data() LOCKS_EXCLUDED(lock_);
  void LockData() EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void UnlockData() EXCLUSIVE_LOCKS_REQUIRED(lock_);
  size_t size() const;

  bool is_frozen() const { return !frozen_time_.is_null(); }

  bool ShouldReschedule() const LOCKS_EXCLUDED(lock_) {
    base::AutoLock lock(lock_);
    return TransientlyUnableToPark();
  }

  // Attempt to park to disk. Returns false if it cannot be parked right now for
  // whatever reason, true if we will _attempt_ to park it to disk.
  bool MaybePark(scoped_refptr<base::SingleThreadTaskRunner> task_runner)
      LOCKS_EXCLUDED(lock_);

  // Unpark the data from disk. This is blocking, on the same thread (since we
  // cannot expect to continue with anything that needs the data until we have
  // unparked it).
  void Unpark() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Tries to write the data from |rw_buffer_| to disk. Then, if the data is
  // successfully written to disk, posts a task to discard |rw_buffer_|.
  static void WriteToDiskInBackground(
      scoped_refptr<ParkableImageImpl>,
      scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner)
      LOCKS_EXCLUDED(lock_);

  // Writes the data referred to by |on_disk_metadata| from disk into the
  // provided |buffer|.
  static size_t ReadFromDiskIntoBuffer(DiskDataMetadata* on_disk_metadata,
                                       base::span<uint8_t> buffer);

  // Attempt to discard the data. This should only be called after we've written
  // the data to disk. Fails if the image can not be parked at the time this is
  // called for whatever reason.
  void MaybeDiscardData() LOCKS_EXCLUDED(lock_);

  // Discards the data in |rw_buffer_|. Caller is responsible for making sure
  // this is only called when the image can be parked.
  void DiscardData() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Only larger images are parked, see kMinSizeToPark for the threshold used.
  bool is_below_min_parking_size() const;

  // Returns whether the ParkableImageImpl is locked or not. See |Lock| and
  // |Unlock| for details.
  bool is_locked() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
  bool is_on_disk() const EXCLUSIVE_LOCKS_REQUIRED(lock_) {
    return !rw_buffer_ && on_disk_metadata_;
  }
  // Whether or not a failure of trying to park the image now would be
  // transient (e.g. due to not being frozen) or not.
  bool TransientlyUnableToPark() const EXCLUSIVE_LOCKS_REQUIRED(lock_);

  bool CanParkNow() const EXCLUSIVE_LOCKS_REQUIRED(lock_);

  mutable base::Lock lock_;

  std::unique_ptr<RWBuffer> rw_buffer_ GUARDED_BY(lock_);

  std::unique_ptr<ReservedChunk> reserved_chunk_ GUARDED_BY(lock_);
  // Non-null iff we have the data from |rw_buffer_| saved to disk.
  std::unique_ptr<DiskDataMetadata> on_disk_metadata_ GUARDED_BY(lock_);
  // |size_| is only modified on the main thread.
  size_t size_ = 0;
  // |frozen_time_| is only modified on the main thread. |frozen_time_| is the
  // time we've frozen the ParkableImage, or a null value if it's not yet
  // frozen.
  base::TimeTicks frozen_time_;
  // Counts the number of Lock/Unlock calls. Incremented by Lock, decremented by
  // Unlock. The ParkableImageImpl is unlocked iff |lock_depth_| is 0, i.e.
  // we've called Lock and Unlock the same number of times.
  size_t lock_depth_ GUARDED_BY(lock_) = 0;
  bool background_task_in_progress_ GUARDED_BY(lock_) = false;
  bool used_ GUARDED_BY(lock_) = false;

  THREAD_CHECKER(thread_checker_);
};

// Wraps a RWBuffer containing encoded image data. This buffer can be written
// to/read from disk when not needed, to improve memory usage.
class PLATFORM_EXPORT ParkableImage final
    : public ThreadSafeRefCounted<ParkableImage> {
 public:
  // Factory method to construct a ParkableImage.
  static scoped_refptr<ParkableImage> Create(size_t initial_capacity = 0);

  // Creates a read-only snapshot of the ParkableImage. This can be used
  // from other threads.
  scoped_refptr<SegmentReader> MakeROSnapshot();

  // Freezes the ParkableImage. This changes the following:
  // (1) We are no longer allowed to mutate the internal buffer (e.g. via
  // Append);
  // (2) The image may now be parked to disk.
  void Freeze() LOCKS_EXCLUDED(impl_->lock_);

  // Appends data to the ParkableImage. Cannot be called after the ParkableImage
  // has been frozen. (see: Freeze())
  void Append(SharedBuffer* buffer, size_t offset = 0)
      LOCKS_EXCLUDED(impl_->lock_);

  // Returns a copy of the data stored in ParkableImage. Calling this will
  // unpark the image from disk if needed.
  scoped_refptr<SharedBuffer> Data() LOCKS_EXCLUDED(impl_->lock_);

  // Returns the size of the encoded image data stored in the ParkableImage. Can
  // be called even if the image is currently parked, and will not unpark it.
  size_t size() const;

  scoped_refptr<SegmentReader> CreateSegmentReader();

 private:
  friend class ThreadSafeRefCounted<ParkableImage>;
  template <typename T, typename... Args>
  friend scoped_refptr<T> base::MakeRefCounted(Args&&... args);
  friend class ParkableImageManager;
  friend class ParkableImageBaseTest;
  friend class ParkableImageSegmentReader;
  friend class ThreadSafeRefCounted<ParkableImageImpl>;

  explicit ParkableImage(size_t initial_capacity = 0);
  ~ParkableImage();

  // Locks and Unlocks the ParkableImageImpl. A locked ParkableImage cannot be
  // parked. Every call to Lock must have a corresponding call to Unlock.
  void LockData() EXCLUSIVE_LOCKS_REQUIRED(impl_->lock_);
  void UnlockData() EXCLUSIVE_LOCKS_REQUIRED(impl_->lock_);

  bool is_on_disk() const EXCLUSIVE_LOCKS_REQUIRED(impl_->lock_);

  scoped_refptr<ParkableImageImpl> impl_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PARKABLE_IMAGE_H_
