chromium/chrome/browser/ui/ash/clipboard/clipboard_image_model_request.h

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

#ifndef CHROME_BROWSER_UI_ASH_CLIPBOARD_CLIPBOARD_IMAGE_MODEL_REQUEST_H_
#define CHROME_BROWSER_UI_ASH_CLIPBOARD_CLIPBOARD_IMAGE_MODEL_REQUEST_H_

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

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/unguessable_token.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/base/models/image_model.h"

namespace ash {
class ScopedClipboardHistoryPause;
}  // namespace ash

namespace ui {
class ClipboardData;
}  // namespace ui

namespace views {
class WebView;
class Widget;
}  // namespace views

class Profile;

// Renders html in an off-screen WebView, copies the rendered surface, and
// passes the copy through |deliver_image_model_callback_|. If the request takes
// takes more than 5s to load, timeout is declared and the callback is not
// called. If the request is Stop()-ed, the callback is not called.
class ClipboardImageModelRequest : public content::WebContentsDelegate,
                                   public content::WebContentsObserver {
 public:
  using ImageModelCallback = base::OnceCallback<void(ui::ImageModel)>;

  struct Params {
    Params() = delete;
    Params(const base::UnguessableToken& id,
           const std::string& html_markup,
           const gfx::Size& bounding_box_size,
           ImageModelCallback request_finished_callback);
    Params(Params&&);
    Params& operator=(Params&&);
    ~Params();

    // A unique identifier, used to cancel running requests.
    base::UnguessableToken id;
    // Markup being rendered.
    std::string html_markup;

    // The size of the rectangle which encloses the selection region on the
    // original page. It is used to calculate the size of the HTML preview image
    // shown on the clipboard history menu.
    gfx::Size bounding_box_size;

    // The callback to return the results of the request. Not called if the
    // request is stopped via Stop(), or if timeout occurs.
    ImageModelCallback callback;
  };

  using RequestStopCallback = base::RepeatingCallback<void(bool)>;

  struct TestParams {
    TestParams() = delete;
    explicit TestParams(
        RequestStopCallback callback,
        const std::optional<bool>& enforce_auto_resize = std::nullopt);
    TestParams(const TestParams&) = delete;
    TestParams& operator=(const TestParams&) = delete;
    ~TestParams();

    // The callback running when the image model request stops. The boolean
    // value indicates whether the image model request renders web contents
    // in auto resize mode.
    RequestStopCallback callback;

    // When set, `enforce_auto_resize` specifies whether the image model request
    // should be rendered in auto resize mode. If `enforce_auto_resize` is
    // true (or false), auto resize mode is always enabled (or disabled).
    std::optional<bool> enforce_auto_resize;
  };

  // Places `html_markup` on the clipboard and restores the original clipboard
  // contents when destructed.
  class ScopedClipboardModifier {
   public:
    explicit ScopedClipboardModifier(const std::string& html_markup);
    ScopedClipboardModifier(const ScopedClipboardModifier&) = delete;
    ScopedClipboardModifier& operator=(const ScopedClipboardModifier&) = delete;
    ~ScopedClipboardModifier();

   private:
    // Pauses ash::ClipboardHistory for its lifetime.
    std::unique_ptr<ash::ScopedClipboardHistoryPause>
        scoped_clipboard_history_pause_;
    std::unique_ptr<ui::ClipboardData> replaced_clipboard_data_;
  };

  ClipboardImageModelRequest(
      Profile* profile,
      base::RepeatingClosure on_request_finished_callback);
  ClipboardImageModelRequest(const ClipboardImageModelRequest&) = delete;
  ClipboardImageModelRequest operator=(const ClipboardImageModelRequest&) =
      delete;
  ~ClipboardImageModelRequest() override;

  // Renders the HTML in a WebView and attempts to copy the surface. If this
  // fails to load after 5 seconds, OnTimeout is called.
  void Start(Params&& params);

  // The different reasons a request can `Stop()`. These values are logged to
  // UMA. Entries should not be renumbered and numeric values should never be
  // reused. Please keep in sync with "RequestStopReason" in
  // src/tools/metrics/histograms/enums.xml.
  enum class RequestStopReason {
    kFulfilled = 0,
    kTimeout = 1,
    kEmptyResult = 2,
    kMultipleCopyCompletion = 3,
    kRequestCanceled = 4,
    kMaxValue = kRequestCanceled,
  };
  // Stops the request and resets state. `stop_reason` is the reason the request
  // was `Stop()`-ed. |web_view_| is kept alive to enable fast restarting of the
  // request.
  void Stop(RequestStopReason stop_reason);

  // `Stop()`s the request and gets the params of the running request.
  Params StopAndGetParams();

  // Whether the clipboard is being modified by this request.
  bool IsModifyingClipboard() const;

  // Returns whether a request with |request_id| is running, or if any request
  // is running if no |request_id| is supplied.
  bool IsRunningRequest(
      std::optional<base::UnguessableToken> request_id = std::nullopt) const;

  // content::WebContentsDelegate:
  void ResizeDueToAutoResize(content::WebContents* web_contents,
                             const gfx::Size& new_size) override;

  // content::WebContentsObserver:
  void DidStopLoading() override;

  // Configures test parameter.
  static void SetTestParams(TestParams* test_params);

 private:
  // Called when the results of the paste are painted.
  void OnVisualStateChangeFinished(bool done);

  // Posts `CopySurface()` to the task sequence.
  void PostCopySurfaceTask();

  // Copies the rendered HTML.
  void CopySurface();

  // Callback called when the rendered surface is done being copied.
  void OnCopyComplete(float device_scale_factor, const SkBitmap& bitmap);

  // Called when the running request takes too long to complete.
  void OnTimeout();

  // Returns whether the auto-resize mode should be enabled. If auto-resize mode
  // is enabled, Blink decides the preview image's size which may be different
  // from that of the original selection region.
  bool ShouldEnableAutoResizeMode() const;

  // A Widget that is not shown, but forces |web_view_| to render.
  std::unique_ptr<views::Widget> const widget_;

  // Contents view of |widget_|. Owned by |widget_|.
  const raw_ptr<views::WebView> web_view_;

  // Unique identifier for this request run. Empty when there are no running
  // requests.
  base::UnguessableToken request_id_;

  // The HTML being rendered.
  std::string html_markup_;

  // The size of the rectangle enclosing the copied HTML on the original page.
  gfx::Size bounding_box_size_;

  // Whether `DidStopLoading()` was called. Used to prevent the request from
  // responding to load events that happen after the initial load.
  bool did_stop_loading_ = false;

  // Responsible for temporarily replacing contents of the clipboard.
  std::optional<ScopedClipboardModifier> scoped_clipboard_modifier_;

  // Callback used to deliver the rendered ImageModel.
  ImageModelCallback deliver_image_model_callback_;

  // Callback called when this request finishes (via timeout or completion).
  base::RepeatingClosure on_request_finished_callback_;

  // Timer used to abort requests which take longer than 5s to load.
  base::RepeatingTimer timeout_timer_;

  // Time this object was created. Used to log object lifetime.
  const base::TimeTicks request_creation_time_;

  // Time this object started its most recent request.
  base::TimeTicks request_start_time_;

  base::WeakPtrFactory<ClipboardImageModelRequest> weak_ptr_factory_{this};

  // Used to debounce calls to `CopySurface()`.
  base::WeakPtrFactory<ClipboardImageModelRequest>
      copy_surface_weak_ptr_factory_{this};
};

#endif  // CHROME_BROWSER_UI_ASH_CLIPBOARD_CLIPBOARD_IMAGE_MODEL_REQUEST_H_