// Copyright 2017 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_MODULES_CLIPBOARD_CLIPBOARD_PROMISE_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_CLIPBOARD_CLIPBOARD_PROMISE_H_

#include <cstdint>
#include <utility>

#include "base/sequence_checker.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "third_party/blink/public/mojom/clipboard/clipboard.mojom-blink.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_blob_string.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_item.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_reader.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"

namespace blink {

class Blob;
class ClipboardWriter;
class LocalFrame;
class ExceptionState;
class ExecutionContext;
class ClipboardItem;
class ClipboardReadOptions;
class SystemClipboard;

// Represents a promise to execute Async Clipboard API functions off the main
// thread. It handles read and write operations on the clipboard, including
// reading and writing text and blobs for different MIME types. This class also
// interacts with the `PermissionService` to check for read and write
// permissions. It uses a `ClipboardItem` object to read/write supported MIME
// types. Spec: https://w3c.github.io/clipboard-apis/#async-clipboard-api
class MODULES_EXPORT ClipboardPromise final
    : public GarbageCollected<ClipboardPromise>,
      public ExecutionContextLifecycleObserver,
      public ClipboardReaderResultHandler {
 public:
  // Creates a promise for reading clipboard data.
  // Spec: https://w3c.github.io/clipboard-apis/#dom-clipboard-read
  // `formats`: Unsanitized formats to be read from the clipboard.
  // Spec:
  // https://w3c.github.io/clipboard-apis/#dom-clipboardunsanitizedformats-unsanitized
  static ScriptPromise<IDLSequence<ClipboardItem>> CreateForRead(
      ExecutionContext* execution_context,
      ScriptState* script_state,
      ClipboardReadOptions* options,
      ExceptionState& exception_state);

  // Creates a promise for reading plain text from the clipboard.
  // Spec: https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext
  static ScriptPromise<IDLString> CreateForReadText(
      ExecutionContext* execution_context,
      ScriptState* script_state,
      ExceptionState& exception_state);

  // Creates a promise for writing supported MIME types to the clipboard.
  // Spec: https://w3c.github.io/clipboard-apis/#dom-clipboard-write
  static ScriptPromise<IDLUndefined> CreateForWrite(
      ExecutionContext* execution_context,
      ScriptState* script_state,
      const HeapVector<Member<ClipboardItem>>& items,
      ExceptionState& exception_state);

  // Creates a promise for writing text to the clipboard.
  // `text`: The text to be written to the clipboard.
  // Spec: https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext
  static ScriptPromise<IDLUndefined> CreateForWriteText(
      ExecutionContext* execution_context,
      ScriptState* script_state,
      const String& text,
      ExceptionState& exception_state);

  // Use one of the above factories to construct. This ctor is public for
  // `MakeGarbageCollected<>`.
  ClipboardPromise(ExecutionContext* execution_context,
                   ScriptPromiseResolverBase*,
                   ExceptionState& exception_state);
  ClipboardPromise(const ClipboardPromise&) = delete;
  ClipboardPromise& operator=(const ClipboardPromise&) = delete;
  ~ClipboardPromise() override;

  // Finishes writing the current representation and prepares for the next one.
  void CompleteWriteRepresentation();

  // Handles rejections originating from the ClipboardWriter.
  void RejectFromReadOrDecodeFailure();

  // TODO(crbug.com/487128731): Remove ClipboardReaderResultHandler interface
  // from ClipboardPromise once lazy read getType is stable.
  // Adds the given `blob` to the `clipboard_item_data_`.
  void OnRead(Blob* blob, const String& mime_type) override;

  // Returns the local frame associated with the promise.
  LocalFrame* GetLocalFrame() const override;

  // Returns the execution context associated with the promise.
  ExecutionContext* GetExecutionContext() const override {
    return ExecutionContextLifecycleObserver::GetExecutionContext();
  }

  SystemClipboard* GetSystemClipboard() const;

  // Same as GetSystemClipboard(), but rejects the promise before returning
  // null, so a detached frame doesn't leave the promise unsettled.
  SystemClipboard* GetSystemClipboardOrReject();

  // Reject the promise and return true if read permission came from the
  // implicit paste event grant and the clipboard has changed since the paste
  // event started.
  bool RejectIfClipboardChangedSincePasteStart(SystemClipboard& clipboard);

  // Returns the script state associated with the promise.
  ScriptState* GetScriptState() const;
  // ExecutionContextLifecycleObserver
  void Trace(Visitor* visitor) const override;

 private:
  class ClipboardItemDataPromiseFulfill;
  class ClipboardItemDataPromiseReject;
  void HandlePromiseWrite(
      HeapVector<Member<V8UnionBlobOrString>> clipboard_item_list);
  void WriteClipboardItemData(
      GCedHeapVector<Member<V8UnionBlobOrString>>* clipboard_item_list);

  // Rejects the promise for blobs that have invalid MIME types or got rejected
  // with the given exception.
  void RejectClipboardItemPromise(ScriptValue);
  void WriteNextRepresentation();

  // Checks Read/Write permission (interacting with `PermissionService`).
  void HandleRead(ClipboardReadOptions* options);
  void HandleReadText();
  void HandleWrite(const HeapVector<Member<ClipboardItem>>& items);
  void HandleWriteText(const String& text);

  // Reads/Writes after permission check.
  void HandleReadWithPermission(mojom::blink::PermissionStatusWithDetailsPtr);
  void HandleReadTextWithPermission(
      mojom::blink::PermissionStatusWithDetailsPtr);
  void HandleWriteWithPermission(mojom::blink::PermissionStatusWithDetailsPtr);
  void HandleWriteTextWithPermission(
      mojom::blink::PermissionStatusWithDetailsPtr);

#if BUILDFLAG(IS_MAC)
  // Callback for macOS platform permission check for readText()
  void OnPlatformPermissionResultForReadText(
      mojom::blink::PlatformClipboardPermissionState state);

  // Callback for macOS platform permission check for read()
  void OnPlatformPermissionResultForRead(
      mojom::blink::PlatformClipboardPermissionState state);
#endif

  // Callback function called when the available format names for reading are
  // received from the clipboard.
  // `format_names`: The available format names on the clipboard
  void OnReadAvailableFormatNames(const Vector<String>& format_names);

  // Reads the next clipboard representation.
  void ReadNextRepresentation();

  // Resolves the read promise.
  void ResolveRead();

  // Callback for the asynchronous SystemClipboard::ReadPlainText used by
  // navigator.clipboard.readText(). Resolves `script_promise_resolver_`.
  void OnReadPlainText(const String& text);

  // Returns the `PermissionService` associated with the promise, or nullptr if
  // the remote connection fails.
  mojom::blink::PermissionService* GetPermissionService();

  // Validates that the action may proceed, including but not limited to
  // requesting permissions from the `PermissionService` as necessary.
  // On failure, will reject via `script_promise_resolver_`.
  //
  // `permission`: The permission to request.
  // `will_be_sanitized`: Whether the data will be sanitized.
  // `callback`: The callback function to be called with the permission status.
  void ValidatePreconditions(
      mojom::blink::PermissionName permission,
      bool will_be_sanitized,
      base::OnceCallback<void(mojom::blink::PermissionStatusWithDetailsPtr)>
          callback);

  scoped_refptr<base::SingleThreadTaskRunner> GetClipboardTaskRunner();

  // ExecutionContextLifecycleObserver
  void ContextDestroyed() override;

  Member<ScriptPromiseResolverBase> script_promise_resolver_;
  Member<ClipboardWriter> clipboard_writer_;
  HeapMojoRemote<mojom::blink::PermissionService> permission_service_;
  // When true, the HTML data read from the clipboard will be unprocessed.
  // When false, the HTML data is processed by the fragment parser.
  bool will_read_unprocessed_html_ = false;
  // Plain text data to be written to the clipboard.
  String plain_text_;
  // Clipboard sequence number as of the start of the paste event whose implicit
  // permission grant this read is riding.
  std::optional<absl::uint128> sequence_number_at_paste_start_;
  // The list of formats read from the clipboard.
  HeapVector<std::pair<String, Member<V8UnionBlobOrString>>>
      clipboard_item_data_;
  // The list of formats with their corresponding promises to the Blob data to
  // be written to the clipboard.
  HeapVector<std::pair<String, MemberScriptPromise<V8UnionBlobOrString>>>
      clipboard_item_data_with_promises_;
  wtf_size_t clipboard_representation_index_ = 0;
  // List of custom format with "web " prefix.
  Vector<String> write_custom_format_types_;
  // Stores the types provided by the web authors.
  Vector<String> write_clipboard_item_types_;
  // Stores the types that the web author requested to receive for a clipboard
  // read operation
  std::optional<HashSet<String>> read_clipboard_item_types_;
  HeapVector<String> item_mime_types_;
  // Cumulative size of blobs read in eager mode for telemetry.
  // Uses uint64_t to match Blob::size() return type and avoid truncation on
  // 32-bit platforms.
  uint64_t total_eager_read_blob_size_ = 0;
  // Sequence number snapshotted before format enumeration so the lazy-read
  // path can detect a clipboard change during the async IPC.
  // See crbug.com/498411773.
  std::optional<absl::uint128> sequence_number_at_read_start_;
  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CLIPBOARD_CLIPBOARD_PROMISE_H_
