// 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.

#include "pdf/pdf_view_web_plugin.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/auto_reset.h"
#include "base/byte_size.h"
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/debug/crash_logging.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/char_iterator.h"
#include "base/i18n/rtl.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/values.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/paint_image.h"
#include "cc/paint/paint_image_builder.h"
#include "net/cookies/site_for_cookies.h"
#include "pdf/accessibility.h"
#include "pdf/accessibility_structs.h"
#include "pdf/buildflags.h"
#include "pdf/content_restriction.h"
#include "pdf/document_layout.h"
#include "pdf/loader/result_codes.h"
#include "pdf/loader/url_loader.h"
#include "pdf/message_util.h"
#include "pdf/metrics_handler.h"
#include "pdf/mojom/pdf.mojom.h"
#include "pdf/paint_manager.h"
#include "pdf/paint_ready_rect.h"
#include "pdf/parsed_params.h"
#include "pdf/pdf_accessibility_data_handler.h"
#include "pdf/pdf_features.h"
#include "pdf/pdf_init.h"
#include "pdf/pdfium/pdfium_engine.h"
#include "pdf/pdfium/pdfium_engine_client.h"
#include "pdf/post_message_receiver.h"
#include "pdf/text_search.h"
#include "pdf/ui/document_properties.h"
#include "pdf/ui/file_name.h"
#include "pdf/ui/thumbnail.h"
#include "printing/metafile_skia.h"
#include "printing/units.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_keyboard_event.h"
#include "third_party/blink/public/common/metrics/document_update_reason.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/mojom/input/focus_type.mojom-shared.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_input_event_result.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_text_input_type.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/web_associated_url_loader.h"
#include "third_party/blink/public/web/web_associated_url_loader_options.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_plugin_container.h"
#include "third_party/blink/public/web/web_plugin_params.h"
#include "third_party/blink/public/web/web_print_params.h"
#include "third_party/blink/public/web/web_print_preset_options.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/public/web/web_widget.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSize.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/text/bytes_formatting.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/gfx/range/range.h"
#include "url/gurl.h"
#include "v8/include/v8.h"

#if BUILDFLAG(ENABLE_PDF_INK2)
#include "base/memory/raw_ref.h"
#include "pdf/pdf_ink_ids.h"
#include "pdf/pdf_ink_metrics_handler.h"
#include "pdf/pdf_ink_module.h"
#include "pdf/pdf_ink_module_client.h"
#include "pdf/pdf_ink_text.h"
#include "third_party/skia/include/core/SkCanvas.h"
#endif

namespace chrome_pdf {

namespace {

// The minimum zoom level allowed.
constexpr double kMinZoom = 0.01;

// A delay to wait between each accessibility page to keep the system
// responsive.
constexpr base::TimeDelta kAccessibilityPageDelay = base::Milliseconds(100);

constexpr base::TimeDelta kFindResultCooldown = base::Milliseconds(100);

constexpr std::string_view kChromeExtensionHost =
    "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/";

// Print Preview base URL.
constexpr std::string_view kChromePrintHost = "chrome://print/";

// Untrusted Print Preview base URL.
constexpr std::string_view kChromeUntrustedPrintHost =
    "chrome-untrusted://print/";

// Same value as `printing::COMPLETE_PREVIEW_DOCUMENT_INDEX`.
constexpr int kCompletePDFIndex = -1;

// A different negative value to differentiate itself from `kCompletePDFIndex`.
constexpr int kInvalidPDFIndex = -2;

// Get save data from plugin in maximum 16 MB blocks.
// LINT.IfChange(MaxSaveBufferSize)
constexpr uint32_t kMaxSaveBufferSize = 16 * 1000 * 1000;
// LINT.ThenChange(//chrome/browser/resources/pdf/pdf_viewer.ts:MaxSaveBufferSize)

// Enumeration of pinch states.
// LINT.IfChange(PinchPhase)
enum class PinchPhase {
  kNone = 0,
  kStart = 1,
  kUpdateZoomOut = 2,
  kUpdateZoomIn = 3,
  kEnd = 4,
};
// LINT.ThenChange(//chrome/browser/resources/pdf/viewport.ts:PinchPhase)

// Initialization performed per renderer process. Initialization may be
// triggered from multiple plugin instances, but should only execute once.
//
// TODO(crbug.com/40147027): We may be able to simplify this once we've figured
// out exactly which processes need to initialize and shutdown PDFium.
class PerProcessInitializer final {
 public:
  ~PerProcessInitializer() {
    // On some configs, thread checker is trivially destructible, which makes
    // `PerProcessInitializer` trivially destructible as well. This is a problem
    // because `base::NoDestructor` only allows non-trivially destructible
    // types. Force `PerProcessInitializer` to be non-trivially destructible by
    // declaring a non-default destructor.
  }

  static PerProcessInitializer& GetInstance() {
    static base::NoDestructor<PerProcessInitializer> instance;
    return *instance;
  }

  void Acquire(bool use_skia) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

    DCHECK_GE(init_count_, 0);
    if (init_count_++ > 0) {
      return;
    }

    DCHECK(!IsSDKInitializedViaPlugin());
    InitializeSDK(/*enable_v8=*/true, use_skia, FontMappingMode::kBlink);
    SetIsSDKInitializedViaPlugin(true);
  }

  void Release() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

    DCHECK_GT(init_count_, 0);
    if (--init_count_ > 0) {
      return;
    }

    DCHECK(IsSDKInitializedViaPlugin());
    ShutdownSDK();
    SetIsSDKInitializedViaPlugin(false);
  }

 private:
  int init_count_ GUARDED_BY_CONTEXT(thread_checker_) = 0;

  // TODO(crbug.com/40147080): Assuming PDFium is thread-hostile for now, and
  // must use one thread exclusively.
  THREAD_CHECKER(thread_checker_);
};

base::DictValue DictFromRect(const gfx::Rect& rect) {
  return base::DictValue()
      .Set("x", rect.x())
      .Set("y", rect.y())
      .Set("width", rect.width())
      .Set("height", rect.height());
}

bool IsPrintPreviewUrl(std::string_view url) {
  return base::StartsWith(url, kChromeUntrustedPrintHost);
}

int ExtractPrintPreviewPageIndex(std::string_view src_url) {
  // Sample `src_url` format:
  // chrome-untrusted://print/token/page_index/print.pdf
  // The page_index is zero-based, but can be negative with special meanings.
  std::vector<std::string_view> url_substr =
      base::SplitStringPiece(src_url.substr(kChromeUntrustedPrintHost.size()),
                             "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  if (url_substr.size() != 3) {
    return kInvalidPDFIndex;
  }

  if (url_substr[2] != "print.pdf") {
    return kInvalidPDFIndex;
  }

  int page_index = 0;
  if (!base::StringToInt(url_substr[1], &page_index)) {
    return kInvalidPDFIndex;
  }
  return page_index;
}

bool IsPreviewingPDF(int print_preview_page_count) {
  return print_preview_page_count == 0;
}

bool IsSaveDataSizeValid(size_t size) {
  return size > 0 && size <= PdfViewWebPlugin::kMaximumSavedFileSize;
}

base::DictValue CreateSaveDataBlockMessage(
    const std::string& token,
    PdfViewWebPlugin::SaveDataBlock data) {
  return base::DictValue()
      .Set("type", "saveDataBlock")
      .Set("token", token)
      .Set("dataToSave", std::move(data.block))
      .Set("totalFileSize", base::checked_cast<int>(data.total_file_size));
}

pdf::mojom::SaveRequestType ParseSaveRequestType(
    const std::string& save_request_type) {
  if (save_request_type == "ANNOTATION") {
    return pdf::mojom::SaveRequestType::kAnnotation;
  } else if (save_request_type == "ORIGINAL") {
    return pdf::mojom::SaveRequestType::kOriginal;
  } else if (save_request_type == "EDITED") {
    return pdf::mojom::SaveRequestType::kEdited;
  } else if (save_request_type == "SEARCHIFIED") {
    return pdf::mojom::SaveRequestType::kSearchified;
  }
  NOTREACHED();
}

void ReleaseBuffer(std::vector<uint8_t>& buffer) {
  std::vector<uint8_t> empty;
  buffer.swap(empty);
}

}  // namespace

#if BUILDFLAG(ENABLE_PDF_INK2)
class PdfViewWebPlugin::PdfInkModuleClientImpl : public PdfInkModuleClient {
 public:
  explicit PdfInkModuleClientImpl(PdfViewWebPlugin& plugin) : plugin_(plugin) {}
  ~PdfInkModuleClientImpl() override = default;

  // PdfInkModuleClient:
  void AddFont(FontId font_id,
               const std::string& font_name,
               base::span<const uint8_t> serialized_typeface) override {
    plugin_->engine_->AddFont(font_id, font_name, serialized_typeface);
  }

  void ClearSelection() override { plugin_->engine_->ClearTextSelection(); }

  void DiscardStroke(int page_index, InkStrokeId id) override {
    plugin_->engine_->DiscardStroke(page_index, id);
  }

  void DiscardText(InkTextId id) override { plugin_->engine_->DiscardText(id); }

  void DrawText(int page_index,
                InkTextId id,
                base::span<const InkTextInfo> text_info,
                float ascent,
                double pdf_zoom,
                const InkTextBoxAttributes& attributes) override {
    plugin_->engine_->DrawText(page_index, id, text_info, ascent, pdf_zoom,
                               attributes);
  }

  void ExtendSelectionByPoint(const gfx::PointF& point) override {
    plugin_->engine_->ExtendSelectionByPoint(point);
  }

  gfx::Transform GetCanonicalToPdfTransform(int page_index) override {
    return plugin_->engine_->GetCanonicalToPdfTransform(page_index);
  }

  ui::Cursor GetCursor() override { return plugin_->cursor_; }

  PageOrientation GetOrientation() const override {
    return plugin_->engine_->GetCurrentOrientation();
  }

  gfx::Rect GetPageContentsRect(int page_index) override {
    if (page_index < 0 || page_index >= plugin_->engine_->GetNumberOfPages()) {
      return gfx::Rect();
    }
    return plugin_->engine_->GetPageContentsRect(page_index);
  }

  gfx::SizeF GetPageSizeInPoints(int page_index) override {
    if (page_index < 0 || page_index >= plugin_->engine_->GetNumberOfPages()) {
      return gfx::SizeF();
    }
    return plugin_->engine_->GetPageSizeInPoints(page_index).value();
  }

  PdfCaret* GetPdfCaret() override { return plugin_->engine_->caret(); }

  PdfInkModuleClient::SelectionRectMap GetSelectionRectMap() override {
    return plugin_->engine_->GetSelectionRectMap();
  }

  gfx::Size GetThumbnailSize(int page_index) override {
    return plugin_->engine_->GetThumbnailSize(page_index,
                                              plugin_->device_scale_);
  }

  gfx::Vector2dF GetViewportOriginOffset() override {
    return plugin_->available_area_.OffsetFromOrigin();
  }

  float GetZoom() const override {
    return plugin_->zoom_ * plugin_->client_->DeviceScaleFactor();
  }

  void Invalidate(const gfx::Rect& rect) override {
    return plugin_->Invalidate(rect);
  }

  bool IsPageVisible(int page_index) override {
    return plugin_->engine_->IsPageVisible(page_index);
  }

  bool IsSelectableTextOrLinkArea(const gfx::PointF& point) override {
    return plugin_->engine_->IsSelectableTextOrLinkArea(point);
  }

  DocumentInkTextBoxesMap LoadTextAnnotationsFromPdf() override {
    return plugin_->engine_->LoadTextAnnotationsFromPdf();
  }

  DocumentV2InkPathShapesMap LoadV2InkPathsFromPdf() override {
    DocumentV2InkPathShapesMap shapes_map;

    for (int i = 0; i < plugin_->engine_->GetNumberOfPages(); ++i) {
      PageV2InkPathShapesMap page_shapes_map =
          plugin_->engine_->LoadV2InkPathsForPage(i);
      if (page_shapes_map.empty()) {
        continue;
      }

      shapes_map[i] = std::move(page_shapes_map);
    }

    return shapes_map;
  }

  void OnAnnotationModeToggled(bool enable) override {
    plugin_->engine_->SetFormHighlight(/*enable_form=*/!enable);
    if (enable) {
      plugin_->engine_->ClearTextSelection();
    }
  }

  void OnTextOrLinkAreaClick(const gfx::PointF& point,
                             int click_count) override {
    plugin_->engine_->OnTextOrLinkAreaClick(point, click_count);
  }

  int PageIndexFromPoint(const gfx::PointF& point) override {
    return PageIndexFromPointImpl(point, /*must_be_visible=*/false);
  }

  void PostMessage(base::DictValue message) override {
    plugin_->client_->PostMessage(std::move(message));
  }

  void RequestThumbnail(int page_index,
                        SendThumbnailCallback callback) override {
    plugin_->engine_->RequestThumbnail(page_index, plugin_->device_scale_,
                                       std::move(callback));
  }

  void StrokeAdded(int page_index,
                   InkStrokeId id,
                   const ink::Stroke& stroke) override {
    plugin_->engine_->ApplyStroke(page_index, id, stroke);

    // `snapshot_` is now stale, since its content doesn't contain this newly
    // added stroke.
    plugin_->snapshot_needs_update_for_ink_input_ = true;
  }

  void StrokeFinished(bool modified) override {
    plugin_->client_->PostMessage(base::DictValue()
                                      .Set("type", "finishInkStroke")
                                      .Set("modified", modified));
    if (modified) {
      plugin_->SetPluginCanSave(true);
    }
  }

  void StrokeStarted() override {
    base::DictValue message;
    message.Set("type", "startInkStroke");
    plugin_->client_->PostMessage(std::move(message));
  }

  void UpdateInkCursor(const ui::Cursor& cursor) override {
    plugin_->cursor_ = cursor;
  }

  void UpdateShapeActive(int page_index,
                         InkModeledShapeId id,
                         bool active) override {
    plugin_->engine_->UpdateShapeActive(page_index, id, active);
  }

  void UpdateStrokeActive(int page_index,
                          InkStrokeId id,
                          bool active) override {
    plugin_->engine_->UpdateStrokeActive(page_index, id, active);
  }

  void UpdateTextActiveAndInvalidate(TextId id, bool active) override {
    plugin_->engine_->UpdateTextActiveAndInvalidate(id, active);
  }

  int VisiblePageIndexFromPoint(const gfx::PointF& point) override {
    return PageIndexFromPointImpl(point, /*must_be_visible=*/true);
  }

 private:
  int PageIndexFromPointImpl(const gfx::PointF& point, bool must_be_visible) {
    for (int i = 0; i < plugin_->engine_->GetNumberOfPages(); ++i) {
      if (must_be_visible && !IsPageVisible(i)) {
        continue;
      }

      // Explicitly construct a gfx::RectF from gfx::Rect, so the Contains()
      // call below works with `point`, which has float values.
      gfx::RectF rect(plugin_->engine_->GetPageContentsRect(i));
      if (!rect.Contains(point)) {
        continue;
      }
      return i;
    }
    return -1;
  }

  const raw_ref<PdfViewWebPlugin> plugin_;
};
#endif  // BUILDFLAG(ENABLE_PDF_INK2)

PdfViewWebPlugin::SaveDataBlock::SaveDataBlock() = default;
PdfViewWebPlugin::SaveDataBlock::SaveDataBlock(
    PdfViewWebPlugin::SaveDataBlock&& other) noexcept = default;
PdfViewWebPlugin::SaveDataBlock& PdfViewWebPlugin::SaveDataBlock::operator=(
    PdfViewWebPlugin::SaveDataBlock&&) noexcept = default;
PdfViewWebPlugin::SaveDataBlock::~SaveDataBlock() = default;

std::unique_ptr<PDFiumEngine> PdfViewWebPlugin::Client::CreateEngine(
    PDFiumEngineClient* client,
    PDFiumFormFiller::ScriptOption script_option) {
  return std::make_unique<PDFiumEngine>(client, script_option);
}

PdfViewWebPlugin::PdfViewWebPlugin(
    std::unique_ptr<Client> client,
    mojo::AssociatedRemote<pdf::mojom::PdfHost> pdf_host,
    blink::WebPluginParams params)
    : blink::WebViewObserver(nullptr),
      client_(std::move(client)),
      pdf_host_(std::move(pdf_host)),
#if BUILDFLAG(ENABLE_PDF_INK2)
      ink_module_client_(MaybeCreatePdfInkModuleClient(*this)),
      ink_module_(MaybeCreatePdfInkModule(ink_module_client_.get())),
#endif
      initial_params_(std::move(params)),
      max_save_buffer_size_(kMaxSaveBufferSize) {
  DCHECK(pdf_host_);
  pdf_host_->SetListener(listener_receiver_.BindNewPipeAndPassRemote());
  if (base::FeatureList::IsEnabled(
          chrome_pdf::features::kPdfGetSaveDataInBlocks)) {
    SetPluginCanSave(true);
  }
}

PdfViewWebPlugin::~PdfViewWebPlugin() = default;

bool PdfViewWebPlugin::Initialize(blink::WebPluginContainer* container) {
  DCHECK(container);
  client_->SetPluginContainer(container);
  DCHECK_EQ(container->Plugin(), this);

  return InitializeCommon();
}

bool PdfViewWebPlugin::InitializeForTesting() {
  return InitializeCommon();
}

bool PdfViewWebPlugin::InitializeCommon() {
  // Allow the plugin to handle touch events.
  client_->RequestTouchEventType(
      blink::WebPluginContainer::kTouchEventRequestTypeRaw);

  // Allow the plugin to handle find requests.
  client_->UsePluginAsFindHandler();

  std::optional<ParsedParams> params = ParseWebPluginParams(initial_params_);

  // The contents of `initial_params_` are no longer needed.
  initial_params_ = {};

  if (!params.has_value()) {
    return false;
  }

  // Sets crash keys like `ppapi::proxy::PDFResource::SetCrashData()`. Note that
  // we don't set the active URL from the top-level URL, as unlike within a
  // plugin process, the active URL changes frequently within a renderer process
  // (see crbug.com/1266050 for details).
  //
  // TODO(crbug.com/40801869): If multiple PDF plugin instances share the same
  // renderer process, the crash key will be overwritten by the newest value.
  static base::debug::CrashKeyString* subresource_url =
      base::debug::AllocateCrashKeyString("subresource_url",
                                          base::debug::CrashKeySize::Size256);
  base::debug::SetCrashKeyString(subresource_url, params->original_url);

  use_skia_renderer_ = params->use_skia;
  PerProcessInitializer::GetInstance().Acquire(params->use_skia);
  initialized_ = true;

  // Check if the PDF is being loaded in the PDF chrome extension. We only allow
  // the plugin to be loaded in the extension and print preview to avoid
  // exposing sensitive APIs directly to external websites.
  //
  // This is enforced before creating the plugin (see
  // `pdf::CreateInternalPlugin()`), so we just `CHECK` for defense-in-depth.
  const std::string& embedder_origin = client_->GetEmbedderOriginString();
  is_print_preview_ = (embedder_origin == kChromePrintHost);
  CHECK(IsPrintPreview() || embedder_origin == kChromeExtensionHost);

  full_frame_ = params->full_frame;
  background_color_ = params->background_color;

  engine_ = client_->CreateEngine(this, params->script_option);
  DCHECK(engine_);

  SendSetSmoothScrolling();

  pdf_accessibility_data_handler_ =
      client_->CreateAccessibilityDataHandler(this, client_->PluginContainer());
  CHECK(pdf_accessibility_data_handler_);

  // Skip the remaining initialization when in Print Preview mode. Loading will
  // continue after the plugin receives a "resetPrintPreviewMode" message.
  if (IsPrintPreview()) {
    return true;
  }

  last_progress_sent_ = 0;
  LoadUrl(params->src_url, base::BindOnce(&PdfViewWebPlugin::DidOpen,
                                          weak_factory_.GetWeakPtr()));
  url_ = params->original_url;

  metrics_handler_ = std::make_unique<MetricsHandler>();
  metrics_handler_->RecordUrlSchemeIsFile(GURL(url_).SchemeIsFile());
  return true;
}

void PdfViewWebPlugin::SendSetSmoothScrolling() {
  client_->PostMessage(
      base::DictValue()
          .Set("type", "setSmoothScrolling")
          .Set("smoothScrolling",
               blink::Platform::Current()->IsScrollAnimatorEnabled()));
}

void PdfViewWebPlugin::DidOpen(std::unique_ptr<UrlLoader> loader,
                               Result result) {
  if (result == Result::kSuccess) {
    if (!engine_->HandleDocumentLoad(std::move(loader), url_)) {
      document_load_state_ = DocumentLoadState::kLoading;
      DocumentLoadFailed();
    }
  } else if (result != Result::kErrorAborted) {
    DocumentLoadFailed();
  }
}

void PdfViewWebPlugin::Destroy() {
  // Holds a pointer to `engine_`. So it must be destroyed before `engine_`.
  annotation_agent_.reset();

  if (initialized_) {
    // Explicitly destroy the PDFiumEngine during destruction as it may call
    // back into this object.
    preview_engine_.reset();
    engine_.reset();
    PerProcessInitializer::GetInstance().Release();
  }

  client_->SetPluginContainer(nullptr);

  delete this;
}

blink::WebPluginContainer* PdfViewWebPlugin::Container() const {
  return client_->PluginContainer();
}

v8::Local<v8::Object> PdfViewWebPlugin::V8ScriptableObject(
    v8::Isolate* isolate) {
  if (scriptable_receiver_.IsEmpty()) {
    // TODO(crbug.com/40147080): Messages should not be handled on the renderer
    // main thread.
    scriptable_receiver_.Reset(
        isolate, PostMessageReceiver::Create(
                     isolate, client_->GetWeakPtr(), weak_factory_.GetWeakPtr(),
                     base::SequencedTaskRunner::GetCurrentDefault()));
  }

  return scriptable_receiver_.Get(isolate);
}

bool PdfViewWebPlugin::SupportsKeyboardFocus() const {
  return !IsPrintPreview();
}

void PdfViewWebPlugin::UpdateAllLifecyclePhases(
    blink::DocumentUpdateReason reason) {}

void PdfViewWebPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) {
  // Clip the intersection of the paint rect and the plugin rect, so that
  // painting outside the plugin or the paint rect area can be avoided.
  // Note: `rect` is in CSS pixels. We need to use `css_plugin_rect_`
  // to calculate the intersection.
  SkRect invalidate_rect =
      gfx::RectToSkRect(gfx::IntersectRects(css_plugin_rect_, rect));
  cc::PaintCanvasAutoRestore auto_restore(canvas, /*save=*/true);
  canvas->clipRect(invalidate_rect);

  // Paint with the plugin's background color if the snapshot is not ready.
  if (snapshot_.GetSkImageInfo().isEmpty()) {
    cc::PaintFlags flags;
    flags.setBlendMode(SkBlendMode::kSrc);
    flags.setColor(GetBackgroundColor());
    canvas->drawRect(invalidate_rect, flags);
    return;
  }

  // Layer translate is independent of scaling, so apply first.
  if (!total_translate_.IsZero()) {
    canvas->translate(total_translate_.x(), total_translate_.y());
  }

  // Position layer at plugin origin before layer scaling.
  if (!plugin_rect_.origin().IsOrigin()) {
    canvas->translate(plugin_rect_.x(), plugin_rect_.y());
  }

  if (snapshot_scale_ != 1.0f) {
    canvas->scale(snapshot_scale_, snapshot_scale_);
  }

  canvas->drawImage(snapshot_, 0, 0);

#if BUILDFLAG(ENABLE_PDF_INK2)
  if (!ink_module_) {
    return;
  }

  if (ink_module_->HasInputsToDraw()) {
    SkBitmap sk_bitmap;
    sk_bitmap.allocPixels(
        SkImageInfo::MakeN32Premul(rect.width(), rect.height()));
    SkCanvas sk_canvas(sk_bitmap);
    sk_canvas.clear(SK_ColorTRANSPARENT);
    ink_module_->Draw(sk_canvas);

    sk_sp<SkImage> snapshot = sk_bitmap.asImage();
    CHECK(snapshot);
    snapshot_ink_inputs_ =
        cc::PaintImageBuilder::WithDefault()
            .set_image(std::move(snapshot), cc::PaintImage::GetNextContentId())
            .set_id(cc::PaintImage::GetNextId())
            .set_no_cache(true)
            .TakePaintImage();
    canvas->drawImage(snapshot_ink_inputs_.value(), 0, 0);
  } else if (snapshot_ink_inputs_.has_value()) {
    if (snapshot_needs_update_for_ink_input_) {
      // Still waiting on `snapshot_` to get refreshed to reflect the change
      // for an added stroke, so reapply the last Ink inputs snapshot to avoid
      // a flash of a recently added stroke temporarily disappearing.
      canvas->drawImage(snapshot_ink_inputs_.value(), 0, 0);
    } else {
      // Now safe to discard the previous Ink inputs snapshot.
      snapshot_ink_inputs_.reset();
    }
  }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)
}

void PdfViewWebPlugin::UpdateGeometry(const gfx::Rect& window_rect,
                                      const gfx::Rect& clip_rect,
                                      const gfx::Rect& unobscured_rect,
                                      bool is_visible) {
  // An empty `window_rect` can be received here in the following cases:
  // - If the embedded plugin size is 0.
  // - If the embedded plugin size is not 0, it can come from re-layouts during
  //   the plugin initialization.
  // For either case, there is no need to create a graphic device to display
  // a PDF in an empty window. Since an empty `window_rect` can cause failure
  // to create the graphic device, avoid all updates on the geometries and the
  // device scales used by the plugin, the PaintManager and the PDFiumEngine
  // unless a non-empty `window_rect` is received.
  if (window_rect.IsEmpty()) {
    return;
  }

  OnViewportChanged(window_rect, client_->DeviceScaleFactor());

  gfx::PointF scroll_position = client_->GetScrollPosition();
  // Convert back to CSS pixels.
  scroll_position.Scale(1.0f / device_scale_);
  UpdateScroll(scroll_position);
}

void PdfViewWebPlugin::UpdateScroll(const gfx::PointF& scroll_position) {
  if (stop_scrolling_) {
    return;
  }

  float max_x = std::max(document_size_.width() * static_cast<float>(zoom_) -
                             plugin_dip_size_.width(),
                         0.0f);
  float max_y = std::max(document_size_.height() * static_cast<float>(zoom_) -
                             plugin_dip_size_.height(),
                         0.0f);

  gfx::PointF scaled_scroll_position(
      std::clamp(scroll_position.x(), 0.0f, max_x),
      std::clamp(scroll_position.y(), 0.0f, max_y));
  scaled_scroll_position.Scale(device_scale_);

  engine_->ScrolledToXPosition(scaled_scroll_position.x());
  engine_->ScrolledToYPosition(scaled_scroll_position.y());
}

void PdfViewWebPlugin::UpdateFocus(bool focused,
                                   blink::mojom::FocusType focus_type) {
  if (engine_->has_focus() != focused) {
    engine_->UpdateFocus(focused);
    client_->UpdateTextInputState();

    // Make sure `this` is still alive after the UpdateSelectionBounds() call.
    auto weak_this = weak_factory_.GetWeakPtr();
    client_->UpdateSelectionBounds();
    if (!weak_this) {
      return;
    }
  }

  if (!focused || !SupportsKeyboardFocus()) {
    return;
  }

  if (focus_type != blink::mojom::FocusType::kBackward &&
      focus_type != blink::mojom::FocusType::kForward) {
    return;
  }

  const int modifiers = focus_type == blink::mojom::FocusType::kForward
                            ? blink::WebInputEvent::kNoModifiers
                            : blink::WebInputEvent::kShiftKey;

  blink::WebKeyboardEvent simulated_event(blink::WebInputEvent::Type::kKeyDown,
                                          modifiers, base::TimeTicks());
  simulated_event.windows_key_code = ui::KeyboardCode::VKEY_TAB;
  HandleWebInputEvent(simulated_event);
}

void PdfViewWebPlugin::UpdateVisibility(bool visibility) {}

blink::WebInputEventResult PdfViewWebPlugin::HandleInputEvent(
    const blink::WebCoalescedInputEvent& event,
    ui::Cursor* cursor) {
  const blink::WebInputEventResult result =
      HandleWebInputEvent(event.Event())
          ? blink::WebInputEventResult::kHandledApplication
          : blink::WebInputEventResult::kNotHandled;

  *cursor = cursor_;

  return result;
}

void PdfViewWebPlugin::DidReceiveResponse(
    const blink::WebURLResponse& response) {}

void PdfViewWebPlugin::DidReceiveData(base::span<const char> data) {}

void PdfViewWebPlugin::DidFinishLoading() {}

void PdfViewWebPlugin::DidFailLoading(const blink::WebURLError& error) {}

bool PdfViewWebPlugin::SupportsPaginatedPrint() {
  return true;
}

bool PdfViewWebPlugin::GetPrintPresetOptionsFromDocument(
    blink::WebPrintPresetOptions* print_preset_options) {
  print_preset_options->is_scaling_disabled = !engine_->GetPrintScaling();
  print_preset_options->copies = engine_->GetCopiesToPrint();
  print_preset_options->duplex_mode = engine_->GetDuplexMode();
  print_preset_options->uniform_page_size = engine_->GetUniformPageSizePoints();
  return true;
}

int PdfViewWebPlugin::PrintBegin(const blink::WebPrintParams& print_params) {
  // The returned value is always equal to the number of pages in the PDF
  // document irrespective of the printable area.
  int32_t ret = engine_->GetNumberOfPages();
  if (!ret) {
    return 0;
  }

  if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality)) {
    return 0;
  }

  print_params_ = print_params;
  if (!engine_->HasPermission(DocumentPermission::kPrintHighQuality)) {
    print_params_->rasterize_pdf = true;
  }

  engine_->PrintBegin();
  return ret;
}

void PdfViewWebPlugin::PrintPage(int page_index, cc::PaintCanvas* canvas) {
  // The entire document goes into one metafile. However, it is impossible to
  // know if a call to `PrintPage()` is the last call. Thus, `PrintPage()` just
  // stores the pages to print and the metafile. Eventually, the printed output
  // is generated in `PrintEnd()` and copied over to the metafile.

  // Every `canvas` passed to this method should have a valid `metafile`.
  printing::MetafileSkia* metafile = canvas->GetPrintingMetafile();
  DCHECK(metafile);

  // `pages_to_print_` should be empty iff `printing_metafile_` is not set.
  DCHECK_EQ(pages_to_print_.empty(), !printing_metafile_);

  // The metafile should be the same across all calls for a given print job.
  DCHECK(!printing_metafile_ || (printing_metafile_ == metafile));

  if (!printing_metafile_) {
    printing_metafile_ = metafile;
  }

  pages_to_print_.push_back(page_index);
}

void PdfViewWebPlugin::PrintEnd() {
  if (pages_to_print_.empty()) {
    return;
  }

  print_pages_called_ = true;
  printing_metafile_->InitFromData(
      engine_->PrintPages(pages_to_print_, print_params_.value()));

  if (print_pages_called_) {
    client_->RecordComputedAction("PDF.PrintPage");
  }
  print_pages_called_ = false;
  print_params_.reset();
  engine_->PrintEnd();

  printing_metafile_ = nullptr;
  pages_to_print_.clear();
}

bool PdfViewWebPlugin::HasSelection() const {
  return !selected_text_.IsEmpty();
}

blink::WebString PdfViewWebPlugin::SelectionAsText() const {
  return selected_text_;
}

blink::WebString PdfViewWebPlugin::SelectionAsMarkup() const {
  return selected_text_;
}

bool PdfViewWebPlugin::CanEditText() const {
  return engine_->CanEditText();
}

bool PdfViewWebPlugin::HasEditableText() const {
  return engine_->HasEditableText();
}

bool PdfViewWebPlugin::CanUndo() const {
  return engine_->CanUndo();
}

bool PdfViewWebPlugin::CanRedo() const {
  return engine_->CanRedo();
}

bool PdfViewWebPlugin::CanCopy() const {
  return engine_->HasPermission(DocumentPermission::kCopy);
}

std::optional<base::i18n::TextDirection>
PdfViewWebPlugin::GetFocusedFormTextDirection() const {
  return engine_->GetFocusedFormTextDirection();
}

bool PdfViewWebPlugin::ExecuteEditCommand(const blink::WebString& name,
                                          const blink::WebString& value) {
  if (name == "SelectAll") {
    return SelectAll();
  }

  if (name == "Cut") {
    SendExecutedEditCommand("Cut");
    return Cut();
  }

  if (name == "Copy") {
    // Deliberately do nothing other than call SendExecutedEditCommand(). The
    // caller is expected to separately call CanCopy() and SelectionAsText().
    SendExecutedEditCommand("Copy");
    return false;
  }

  if (name == "Paste" || name == "PasteAndMatchStyle") {
    SendExecutedEditCommand("Paste");
    return Paste(value);
  }

  if (name == "Undo") {
    return Undo();
  }

  if (name == "Redo") {
    return Redo();
  }

  if (name == "MakeTextWritingDirectionLeftToRight") {
    return SetFocusedFormTextDirection(base::i18n::LEFT_TO_RIGHT);
  }

  if (name == "MakeTextWritingDirectionRightToLeft") {
    return SetFocusedFormTextDirection(base::i18n::RIGHT_TO_LEFT);
  }

  if (name == "MakeTextWritingDirectionNatural") {
    // Setting base::i18n::UNKNOWN_DIRECTION falls back to PDFium's
    // auto-direction logic.
    return SetFocusedFormTextDirection(base::i18n::UNKNOWN_DIRECTION);
  }

  return false;
}

blink::WebURL PdfViewWebPlugin::LinkAtPosition(
    const gfx::Point& /*position*/) const {
  return GURL(link_under_cursor_);
}

bool PdfViewWebPlugin::StartFind(const blink::WebString& search_text,
                                 bool case_sensitive,
                                 int identifier) {
  if (find_identifier_ == -1) {
    // Only go through this code path when `find_identifier_` is -1. i.e. The
    // first time the user performs find-in-page, or after a StopFind() call.
    // Since StartFind() gets called every time the user changes `search_text`,
    // if this conditional did not exist, then SendStartedFindInPage() would
    // get called too many times compared to the "Find" action in
    // tools/metrics/actions/actions.xml.
    SendStartedFindInPage();
  }

  ResetRecentlySentFindUpdate();
  find_identifier_ = identifier;
  engine_->StartFind(search_text.Utf16(), case_sensitive);
  return true;
}

void PdfViewWebPlugin::SelectFindResult(bool forward, int identifier) {
  find_identifier_ = identifier;
  engine_->SelectFindResult(forward);
}

void PdfViewWebPlugin::StopFind() {
  find_identifier_ = -1;
  engine_->StopFind();
  tickmarks_.clear();
  client_->ReportFindInPageTickmarks(tickmarks_);
}

bool PdfViewWebPlugin::CanRotateView() {
  return !IsPrintPreview();
}

void PdfViewWebPlugin::RotateView(blink::WebPlugin::RotationType type) {
  DCHECK(CanRotateView());

  switch (type) {
    case blink::WebPlugin::RotationType::k90Clockwise:
      engine_->RotateClockwise();
      return;
    case blink::WebPlugin::RotationType::k90Counterclockwise:
      engine_->RotateCounterclockwise();
      return;
  }
  NOTREACHED();
}

bool PdfViewWebPlugin::ShouldDispatchImeEventsToPlugin() {
  return true;
}

blink::WebTextInputType PdfViewWebPlugin::GetPluginTextInputType() {
  return text_input_type_;
}

gfx::Rect PdfViewWebPlugin::GetPluginCaretBounds() {
  return caret_rect_;
}

void PdfViewWebPlugin::ImeSetCompositionForPlugin(
    const blink::WebString& text,
    const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
    const gfx::Range& /*replacement_range*/,
    int /*selection_start*/,
    int /*selection_end*/) {
  composition_text_ = text;
}

void PdfViewWebPlugin::ImeCommitTextForPlugin(
    const blink::WebString& text,
    const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
    const gfx::Range& /*replacement_range*/,
    int /*relative_cursor_pos*/) {
  HandleImeCommit(text);
}

void PdfViewWebPlugin::ImeFinishComposingTextForPlugin(
    bool /*keep_selection*/) {
  HandleImeCommit(composition_text_);
}

bool PdfViewWebPlugin::SupportsAnnotation() const {
  return true;
}

void PdfViewWebPlugin::BindAnnotationAgentContainer(
    blink::CrossVariantMojoReceiver<
        blink::mojom::AnnotationAgentContainerInterfaceBase> pending_receiver) {
  annotation_agent_container_receiver_.Bind(std::move(pending_receiver));
}

void PdfViewWebPlugin::OnDestruct() {
  // Intentionally empty, as destruction is handled by `Destroy()`.
}

void PdfViewWebPlugin::OnRendererPreferencesUpdated(
    const blink::RendererPreferences& preferences) {
  client_->PostMessage(
      base::DictValue()
          .Set("type", "rendererPreferencesUpdated")
          .Set("caretBrowsingEnabled", preferences.caret_browsing_enabled));
  engine_->SetCaretBrowsingEnabled(preferences.caret_browsing_enabled);
  // Blink interval can be set to 0 to make the caret not blink.
  engine_->SetCaretBlinkInterval(preferences.caret_blink_interval.value_or(
      PdfCaret::kDefaultBlinkInterval));
}

void PdfViewWebPlugin::ProposeDocumentLayout(const DocumentLayout& layout) {
  base::ListValue page_dimensions;
  page_dimensions.reserve(layout.page_count());
  for (size_t i = 0; i < layout.page_count(); ++i) {
    page_dimensions.Append(DictFromRect(layout.page_rect(i)));
  }
  client_->PostMessage(base::DictValue()
                           .Set("type", "documentDimensions")
                           .Set("width", layout.size().width())
                           .Set("height", layout.size().height())
                           .Set("layoutOptions", layout.options().ToValue())
                           .Set("pageDimensions", std::move(page_dimensions)));

  // Reload the accessibility tree on layout changes because the relative page
  // bounds are no longer valid.
  if (layout.dirty() && accessibility_state_ == AccessibilityState::kLoaded) {
    LoadAccessibility();
  }
}

bool PdfViewWebPlugin::UseSkiaPremultipliedAlpha() {
  return use_skia_renderer_ &&
         chrome_pdf::features::kPdfUseSkiaPremultiplied.Get();
}

void PdfViewWebPlugin::Invalidate(const gfx::Rect& rect) {
  if (in_paint_) {
    deferred_invalidates_.push_back(rect);
    return;
  }

  gfx::Rect offset_rect = rect + available_area_.OffsetFromOrigin();
  paint_manager_.InvalidateRect(offset_rect);
}

void PdfViewWebPlugin::DidScroll(const gfx::Vector2d& offset) {
  if (!image_data_.drawsNothing()) {
    paint_manager_.ScrollRect(available_area_, offset);
  }
}

void PdfViewWebPlugin::ScrollToX(int x_screen_coords,
                                 bool force_smooth_scroll) {
  const float x_scroll_pos = x_screen_coords / device_scale_;

  client_->PostMessage(base::DictValue()
                           .Set("type", "setScrollPosition")
                           .Set("x", static_cast<double>(x_scroll_pos))
                           .Set("forceSmoothScroll", force_smooth_scroll));
}

void PdfViewWebPlugin::ScrollToY(int y_screen_coords,
                                 bool force_smooth_scroll) {
  const float y_scroll_pos = y_screen_coords / device_scale_;

  client_->PostMessage(base::DictValue()
                           .Set("type", "setScrollPosition")
                           .Set("y", static_cast<double>(y_scroll_pos))
                           .Set("forceSmoothScroll", force_smooth_scroll));
}

void PdfViewWebPlugin::ScrollBy(const gfx::Vector2d& delta) {
  const float x_delta = delta.x() / device_scale_;
  const float y_delta = delta.y() / device_scale_;

  client_->PostMessage(base::DictValue()
                           .Set("type", "scrollBy")
                           .Set("x", static_cast<double>(x_delta))
                           .Set("y", static_cast<double>(y_delta)));
}

void PdfViewWebPlugin::ScrollToPage(int page) {
  if (!engine_ || engine_->GetNumberOfPages() == 0) {
    return;
  }

  client_->PostMessage(
      base::DictValue().Set("type", "goToPage").Set("page", page));
}

void PdfViewWebPlugin::NavigateTo(const std::string& url,
                                  WindowOpenDisposition disposition) {
  client_->PostMessage(base::DictValue()
                           .Set("type", "navigate")
                           .Set("url", url)
                           .Set("disposition", static_cast<int>(disposition)));
}

void PdfViewWebPlugin::NavigateToDestination(int page,
                                             const float* x,
                                             const float* y,
                                             const float* zoom) {
  auto message =
      base::DictValue().Set("type", "navigateToDestination").Set("page", page);
  if (x) {
    message.Set("x", static_cast<double>(*x));
  }
  if (y) {
    message.Set("y", static_cast<double>(*y));
  }
  if (zoom) {
    message.Set("zoom", static_cast<double>(*zoom));
  }
  client_->PostMessage(std::move(message));
}

void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) {
#if BUILDFLAG(ENABLE_PDF_INK2)
  if (ink_module_ && ink_module_->enabled()) {
    // Block normal mouse cursor updates, so the cursor set by PdfInkModule
    // while it is enabled does not get overwritten.
    return;
  }
#endif

  cursor_ = new_cursor_type;
}

void PdfViewWebPlugin::UpdateTickMarks(
    const std::vector<gfx::Rect>& tickmarks) {
  tickmarks_ = tickmarks;
}

void PdfViewWebPlugin::NotifyNumberOfFindResultsChanged(int total,
                                                        bool final_result) {
  // We don't want to spam the renderer with too many updates to the number of
  // find results. Don't send an update if we sent one too recently. If it's the
  // final update, we always send it though.
  if (recently_sent_find_update_ && !final_result) {
    return;
  }

  // After stopping search and setting `find_identifier_` to -1 there still may
  // be a NotifyNumberOfFindResultsChanged notification pending from engine.
  // Just ignore them.
  if (find_identifier_ != -1) {
    client_->ReportFindInPageMatchCount(find_identifier_, total, final_result);
  }

  client_->ReportFindInPageTickmarks(tickmarks_);

  if (final_result) {
    return;
  }

  recently_sent_find_update_ = true;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&PdfViewWebPlugin::ResetRecentlySentFindUpdate,
                     weak_factory_.GetWeakPtr()),
      kFindResultCooldown);
}

void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index,
                                                       bool final_result) {
  if (find_identifier_ == -1 || !client_->PluginContainer()) {
    return;
  }

  DCHECK_GE(current_find_index, -1);
  client_->ReportFindInPageSelection(find_identifier_, current_find_index + 1,
                                     final_result);
}

void PdfViewWebPlugin::NotifyTouchSelectionOccurred() {
  client_->PostMessage(base::DictValue().Set("type", "touchSelectionOccurred"));
}

void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
  caret_rect_ = caret_rect + available_area_.OffsetFromOrigin();
}

void PdfViewWebPlugin::GetDocumentPassword(
    base::OnceCallback<void(const std::string&)> callback) {
  DCHECK(password_callback_.is_null());
  password_callback_ = std::move(callback);

  client_->PostMessage(base::DictValue().Set("type", "getPassword"));
}

void PdfViewWebPlugin::Beep() {
  client_->PostMessage(base::DictValue().Set("type", "beep"));
}

void PdfViewWebPlugin::Alert(const std::string& message) {
  client_->Alert(blink::WebString::FromUtf8(message));
}

bool PdfViewWebPlugin::Confirm(const std::string& message) {
  return client_->Confirm(blink::WebString::FromUtf8(message));
}

std::string PdfViewWebPlugin::Prompt(const std::string& question,
                                     const std::string& default_answer) {
  return client_
      ->Prompt(blink::WebString::FromUtf8(question),
               blink::WebString::FromUtf8(default_answer))
      .Utf8();
}

std::string PdfViewWebPlugin::GetURL() {
  return url_;
}

void PdfViewWebPlugin::LoadUrl(std::string_view url, LoadUrlCallback callback) {
  UrlRequest request;
  request.url = std::string(url);
  request.method = "GET";
  request.ignore_redirects = true;

  auto loader = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
  UrlLoader* raw_loader = loader.get();
  raw_loader->Open(request,
                   base::BindOnce(std::move(callback), std::move(loader)));
}

void PdfViewWebPlugin::Email(const std::string& to,
                             const std::string& cc,
                             const std::string& bcc,
                             const std::string& subject,
                             const std::string& body) {
  client_->PostMessage(
      base::DictValue()
          .Set("type", "email")
          .Set("to", base::EscapeUrlEncodedData(to, false))
          .Set("cc", base::EscapeUrlEncodedData(cc, false))
          .Set("bcc", base::EscapeUrlEncodedData(bcc, false))
          .Set("subject", base::EscapeUrlEncodedData(subject, false))
          .Set("body", base::EscapeUrlEncodedData(body, false)));
}

void PdfViewWebPlugin::Print() {
  if (!engine_) {
    return;
  }

  const bool can_print =
      engine_->HasPermission(DocumentPermission::kPrintLowQuality) ||
      engine_->HasPermission(DocumentPermission::kPrintHighQuality);
  if (!can_print) {
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&PdfViewWebPlugin::OnInvokePrintDialog,
                                weak_factory_.GetWeakPtr()));
}

void PdfViewWebPlugin::SubmitForm(const std::string& url,
                                  base::span<const uint8_t> data) {
  // `url` might be a relative URL. Resolve it against the document's URL.
  // TODO(crbug.com/40224475): Probably redundant with `Client::CompleteURL()`.
  GURL resolved_url = GURL(url_).Resolve(url);
  if (!resolved_url.is_valid()) {
    return;
  }

  UrlRequest request;
  request.url = resolved_url.spec();
  request.method = "POST";
  request.body.assign(base::as_string_view(data));

  form_loader_ = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
  form_loader_->Open(request, base::BindOnce(&PdfViewWebPlugin::DidFormOpen,
                                             weak_factory_.GetWeakPtr()));
}

void PdfViewWebPlugin::DidFormOpen(Result result) {
  // TODO(crbug.com/41317525): Process response.
  LOG_IF(ERROR, result != Result::kSuccess) << "DidFormOpen failed: " << result;
  form_loader_.reset();
}

void PdfViewWebPlugin::DidStartLoading() {
  if (did_call_start_loading_) {
    return;
  }

  client_->DidStartLoading();
  did_call_start_loading_ = true;
}

void PdfViewWebPlugin::DidStopLoading() {
  if (!did_call_start_loading_) {
    return;
  }

  client_->DidStopLoading();
  did_call_start_loading_ = false;
}

int PdfViewWebPlugin::GetContentRestrictions() const {
  int content_restrictions = kContentRestrictionCut | kContentRestrictionPaste;
  if (!engine_->HasPermission(DocumentPermission::kCopy)) {
    content_restrictions |= kContentRestrictionCopy;
  }

  if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality) &&
      !engine_->HasPermission(DocumentPermission::kPrintHighQuality)) {
    content_restrictions |= kContentRestrictionPrint;
  }

  return content_restrictions;
}

std::unique_ptr<UrlLoader> PdfViewWebPlugin::CreateUrlLoader() {
  if (full_frame_) {
    DidStartLoading();

    // Disable save and print until the document is fully loaded, since they
    // would generate an incomplete document. This needs to be done each time
    // DidStartLoading() is called because that resets the content restrictions.
    pdf_host_->UpdateContentRestrictions(kContentRestrictionSave |
                                         kContentRestrictionPrint);
  }

  return std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
}

v8::Isolate* PdfViewWebPlugin::GetIsolate() {
  return client_->GetIsolate();
}

std::vector<PDFiumEngineClient::SearchStringResult>
PdfViewWebPlugin::SearchString(const std::u16string& needle,
                               const std::u16string& haystack,
                               bool case_sensitive) {
  return TextSearch(/*needle=*/needle, /*haystack=*/haystack, case_sensitive);
}

void PdfViewWebPlugin::DocumentLoadComplete() {
  DCHECK_EQ(DocumentLoadState::kLoading, document_load_state_);
  document_load_state_ = DocumentLoadState::kComplete;

  client_->RecordComputedAction("PDF.LoadSuccess");

  // Clear the focus state for on-screen keyboards.
  FormFieldFocusChange(PDFiumEngineClient::FocusFieldType::kNoFocus);

  if (IsPrintPreview()) {
    // Scroll location is retained across document loads in Print Preview, so
    // there's no need to override the scroll position by scrolling again.
    if (IsPreviewingPDF(print_preview_page_count_)) {
      SendPrintPreviewLoadedNotification();
    } else {
      DCHECK_EQ(0, print_preview_loaded_page_count_);
      print_preview_loaded_page_count_ = 1;
      engine_->AppendBlankPages(print_preview_page_count_);
      LoadNextPreviewPage();
    }

    OnGeometryChanged(0, 0);
    if (!document_size_.IsEmpty()) {
      paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
    }
  }

  RecordDocumentMetrics();

  if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfPortfolio)) {
    SendAttachments();
  }
  SendBookmarks();
  SendMetadata();

  if (accessibility_state_ == AccessibilityState::kPending) {
    LoadAccessibility();
  }

  ApplyAndObserveRendererPreferences();

#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
  // To avoid delaying page load for searchify, start searchify after document
  // load is completed.
  // Maximum image dimension is asked once and stored for the next usages, so
  // `BindOnce` is sufficient.
  client_->SetOcrDisconnectedCallback(engine_->GetOcrDisconnectHandler());
  engine_->StartSearchify(
      base::BindOnce(&Client::GetOcrMaxImageDimension, client_->GetWeakPtr()),
      base::BindRepeating(&Client::PerformOcr, client_->GetWeakPtr()));
#endif

  pdf_host_->OnDocumentLoadComplete();

  if (!full_frame_) {
    return;
  }

  DidStopLoading();
  pdf_host_->UpdateContentRestrictions(GetContentRestrictions());
}

void PdfViewWebPlugin::DocumentLoadFailed() {
  DCHECK_EQ(DocumentLoadState::kLoading, document_load_state_);
  document_load_state_ = DocumentLoadState::kFailed;

  client_->RecordComputedAction("PDF.LoadFailure");

  // Send a progress value of -1 to indicate a failure.
  SendLoadingProgress(-1);

  DidStopLoading();

  paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
}

void PdfViewWebPlugin::DocumentHasUnsupportedFeature(
    const std::string& feature) {
  DCHECK(!feature.empty());
  std::string metric = base::StrCat({"PDF_Unsupported_", feature});
  if (unsupported_features_reported_.insert(metric).second) {
    client_->RecordComputedAction(metric);
  }
}

void PdfViewWebPlugin::DocumentLoadProgress(uint32_t available,
                                            uint32_t doc_size) {
  double progress = 0.0;
  if (doc_size > 0) {
    progress = 100.0 * static_cast<double>(available) / doc_size;
  } else {
    // Use heuristics when the document size is unknown.
    // Progress logarithmically from 0 to 100M.
    static const double kFactor = std::log(100'000'000.0) / 100.0;
    if (available > 0) {
      progress =
          std::min(std::log(static_cast<double>(available)) / kFactor, 100.0);
    }
  }

  // DocumentLoadComplete() will send the 100% load progress.
  if (progress >= 100) {
    return;
  }

  // Avoid sending too many progress messages over PostMessage.
  if (progress <= last_progress_sent_ + 1) {
    return;
  }

  SendLoadingProgress(progress);
}

void PdfViewWebPlugin::FormFieldFocusChange(
    PDFiumEngineClient::FocusFieldType type) {
  std::string field_type;
  // LINT.IfChange(FocusFieldTypes)
  switch (type) {
    case PDFiumEngineClient::FocusFieldType::kNoFocus:
      field_type = "none";
      break;
    case PDFiumEngineClient::FocusFieldType::kNonText:
      field_type = "non-text";
      break;
    case PDFiumEngineClient::FocusFieldType::kText:
      field_type = "text";
      break;
  }
  // LINT.ThenChange(//chrome/browser/resources/pdf/constants.ts:FocusFieldTypes)

  client_->PostMessage(base::DictValue()
                           .Set("type", "formFocusChange")
                           .Set("focused", std::move(field_type)));

  text_input_type_ = type == PDFiumEngineClient::FocusFieldType::kText
                         ? blink::WebTextInputType::kWebTextInputTypeText
                         : blink::WebTextInputType::kWebTextInputTypeNone;
  client_->UpdateTextInputState();
}

bool PdfViewWebPlugin::IsPrintPreview() const {
  return is_print_preview_;
}

SkColor PdfViewWebPlugin::GetBackgroundColor() const {
  return background_color_;
}

void PdfViewWebPlugin::SelectionChanged(const gfx::Rect& left,
                                        const gfx::Rect& right) {
#if BUILDFLAG(ENABLE_PDF_INK2)
  // Ignore the selected text if `ink_module_` is currently text highlighting.
  // This prevents `pdf_host_` from showing touch handles for touch text
  // highlighting.
  if (ink_module_ && ink_module_->ShouldBlockTextSelectionChanged()) {
    return;
  }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)

  gfx::PointF left_point(left.x() + available_area_.x(), left.y());
  gfx::PointF right_point(right.x() + available_area_.x(), right.y());

  const float inverse_scale = 1.0f / device_scale_;
  left_point.Scale(inverse_scale);
  right_point.Scale(inverse_scale);

  pdf_host_->SelectionChanged(left_point, left.height() * inverse_scale,
                              right_point, right.height() * inverse_scale);

  if (accessibility_state_ == AccessibilityState::kLoaded) {
    PrepareAndSetAccessibilityViewportInfo();
  }
}

void PdfViewWebPlugin::EnteredEditMode() {
  edit_mode_ = true;
  SetPluginCanSave(true);

  client_->PostMessage(base::DictValue().Set("type", "setIsEditing"));
}

void PdfViewWebPlugin::DocumentFocusChanged(bool document_has_focus) {
  client_->PostMessage(base::DictValue()
                           .Set("type", "documentFocusChanged")
                           .Set("hasFocus", document_has_focus));
}

void PdfViewWebPlugin::SetSelectedText(const std::string& selected_text) {
#if BUILDFLAG(ENABLE_PDF_INK2)
  // Ignore the selected text if `ink_module_` is currently text highlighting.
  if (ink_module_ && ink_module_->ShouldBlockTextSelectionChanged()) {
    return;
  }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)
  selected_text_ = blink::WebString::FromUtf8(selected_text);
  client_->TextSelectionChanged(selected_text_, /*offset=*/0,
                                gfx::Range(0, selected_text_.length()));
}

void PdfViewWebPlugin::SetLinkUnderCursor(
    const std::string& link_under_cursor) {
  link_under_cursor_ = link_under_cursor;
}

bool PdfViewWebPlugin::IsValidLink(const std::string& url) {
  return base::Value(url).is_string();
}

void PdfViewWebPlugin::OnNewTextFragmentsSearchStarted() {
  if (annotation_agent_) {
    annotation_agent_->RemoveTextFragments();
  }
}

#if BUILDFLAG(ENABLE_PDF_INK2)
bool PdfViewWebPlugin::IsInAnnotationMode() const {
  return ink_module_ && ink_module_->enabled();
}
#endif  // BUILDFLAG(ENABLE_PDF_INK2)

#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
void PdfViewWebPlugin::OnSearchifyStateChange(bool busy) {
  switch (searchify_state_) {
    case SearchifyState::kNotStarted:
      // Expected to be called only to say searchify started.
      CHECK(busy);
      pdf_host_->OnSearchifyStarted();
      searchify_state_ = SearchifyState::kStarted;
      break;

    case SearchifyState::kStarted:
      // Expected to be called only to say searchify stopped.
      CHECK(!busy);
      searchify_state_ = SearchifyState::kStopped;
      break;

    case SearchifyState::kShowingInProgress:
      // Expected to be called only to say searchify stopped.
      CHECK(!busy);
      // Executing the script directly may cause a crash in blink as it might be
      // during layout change, hence posting it (crbug.com/401142034).
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(&PdfViewWebPlugin::SetShowSearchifyInProgress,
                         weak_factory_.GetWeakPtr(), /*show=*/false));
      searchify_state_ = SearchifyState::kStopped;
      break;

    case SearchifyState::kStopped:
      // Expected to be called only to say searchify started again.
      CHECK(busy);
      searchify_state_ = SearchifyState::kStarted;
      break;
  }
}

void PdfViewWebPlugin::MaybeShowSearchifyInProgress() {
  if (searchify_state_ == SearchifyState::kStarted) {
    searchify_state_ = SearchifyState::kShowingInProgress;
    // Executing the script directly may cause a crash in blink as it might be
    // during layout change, hence posting it (crbug.com/401142034).
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&PdfViewWebPlugin::SetShowSearchifyInProgress,
                                  weak_factory_.GetWeakPtr(), /*show=*/true));
  }
}

void PdfViewWebPlugin::SetShowSearchifyInProgress(bool show) {
  client_->PostMessage(base::DictValue()
                           .Set("type", "showSearchifyInProgress")
                           .Set("show", show));
}

void PdfViewWebPlugin::OnHasSearchifyText() {
  client_->PostMessage(base::DictValue().Set("type", "setHasSearchifyText"));

  pdf_accessibility_data_handler_->OnHasSearchifyText();
  if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfSearchifySave)) {
    SetPluginCanSave(true);
  }
}
#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)

void PdfViewWebPlugin::SetCaretPosition(const gfx::PointF& position) {
  engine_->SetCaretPosition(FrameToPdfCoordinates(position));
}

void PdfViewWebPlugin::MoveRangeSelectionExtent(const gfx::PointF& extent) {
  engine_->MoveRangeSelectionExtent(FrameToPdfCoordinates(extent));
}

void PdfViewWebPlugin::SetSelectionBase(const gfx::PointF& base) {
  engine_->SetSelectionBase(FrameToPdfCoordinates(base));
}

void PdfViewWebPlugin::GetPdfBytes(uint32_t size_limit,
                                   GetPdfBytesCallback callback) {
  uint32_t page_count = engine_->GetNumberOfPages();
  if (engine_->GetLoadedByteSize() > size_limit) {
    std::move(callback).Run(GetPdfBytesStatus::kSizeLimitExceeded, {},
                            page_count);
    return;
  }

  std::move(callback).Run(GetPdfBytesStatus::kSuccess, engine_->GetSaveData(),
                          page_count);
}

void PdfViewWebPlugin::GetMostVisiblePageIndex(
    GetMostVisiblePageIndexCallback callback) {
  auto page_index = engine_->GetMostVisiblePage();
  if (page_index < 0) {
    std::move(callback).Run(std::nullopt);
    return;
  }
  std::move(callback).Run(page_index);
}

void PdfViewWebPlugin::HasMeaningfulText(HasMeaningfulTextCallback callback) {
  std::move(callback).Run(engine_ && engine_->HasMeaningfulText());
}

void PdfViewWebPlugin::HasJavaScript(HasJavaScriptCallback callback) {
  std::move(callback).Run(engine_ && engine_->HasJavaScript());
}

void PdfViewWebPlugin::IsPasswordProtected(
    IsPasswordProtectedCallback callback) {
  std::move(callback).Run(engine_ && engine_->IsPasswordProtected());
}

void PdfViewWebPlugin::GetPageText(int32_t page_index,
                                   GetPageTextCallback callback) {
  if (page_index < 0 || page_index >= engine_->GetNumberOfPages()) {
    std::move(callback).Run(std::u16string());
    return;
  }
  std::move(callback).Run(engine_->GetPageText(page_index));
}

#if BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)
void PdfViewWebPlugin::GetSaveDataBufferHandlerForDrive(
    pdf::mojom::SaveRequestType request_type,
    GetSaveDataBufferHandlerForDriveCallback callback) {
  std::unique_ptr<SaveDataBufferHandlerForDrive> buffer_handler;
  if (request_type == pdf::mojom::SaveRequestType::kOriginal) {
    buffer_handler = std::make_unique<OriginalDataHandlerForDrive>(this);
  } else {
    buffer_handler = std::make_unique<ModifiedDataBufferHandlerForDrive>(this);
#if BUILDFLAG(ENABLE_PDF_INK2)
    if (request_type == pdf::mojom::SaveRequestType::kAnnotation &&
        ink_module_) {
      ink_module_->RecordMetricsOnSave();
    }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)
  }
  const uint32_t file_size = buffer_handler->GetFileSize();
  if (!file_size) {
    std::move(callback).Run(nullptr);
    return;
  }
  mojo::PendingRemote<pdf::mojom::SaveDataBufferHandler> remote;
  save_data_buffer_handler_receivers_.Add(
      std::move(buffer_handler), remote.InitWithNewPipeAndPassReceiver());
  std::move(callback).Run(pdf::mojom::SaveDataBufferHandlerGetResult::New(
      std::move(remote), file_size));
}
#endif  // BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)

bool PdfViewWebPlugin::IsValid() const {
  return client_->HasFrame();
}

blink::WebURL PdfViewWebPlugin::CompleteURL(
    const blink::WebString& partial_url) const {
  DCHECK(IsValid());
  return client_->CompleteURL(partial_url);
}

net::SiteForCookies PdfViewWebPlugin::SiteForCookies() const {
  DCHECK(IsValid());
  return client_->SiteForCookies();
}

void PdfViewWebPlugin::SetReferrerForRequest(
    blink::WebURLRequest& request,
    const blink::WebURL& referrer_url) {
  client_->SetReferrerForRequest(request, referrer_url);
}

std::unique_ptr<blink::WebAssociatedURLLoader>
PdfViewWebPlugin::CreateAssociatedURLLoader(
    const blink::WebAssociatedURLLoaderOptions& options) {
  return client_->CreateAssociatedURLLoader(options);
}

void PdfViewWebPlugin::OnMessage(const base::DictValue& message) {
#if BUILDFLAG(ENABLE_PDF_INK2)
  if (ink_module_ && ink_module_->OnMessage(message)) {
    return;
  }
#endif

  using MessageHandler = void (PdfViewWebPlugin::*)(const base::DictValue&);

  static constexpr auto kMessageHandlers =
      base::MakeFixedFlatMap<std::string_view, MessageHandler>({
          {"displayAnnotations",
           &PdfViewWebPlugin::HandleDisplayAnnotationsMessage},
          {"getNamedDestination",
           &PdfViewWebPlugin::HandleGetNamedDestinationMessage},
          {"getPageBoundingBox",
           &PdfViewWebPlugin::HandleGetPageBoundingBoxMessage},
          {"getPasswordComplete",
           &PdfViewWebPlugin::HandleGetPasswordCompleteMessage},
          {"getSaveDataBlock",
           &PdfViewWebPlugin::HandleGetSaveDataBlockMessage},
          {"getSelectedText", &PdfViewWebPlugin::HandleGetSelectedTextMessage},
          {"getSuggestedFileName",
           &PdfViewWebPlugin::HandleGetSuggestedFileName},
          {"getThumbnail", &PdfViewWebPlugin::HandleGetThumbnailMessage},
          {"highlightTextFragments",
           &PdfViewWebPlugin::HandleHighlightTextFragmentsMessage},
          {"print", &PdfViewWebPlugin::HandlePrintMessage},
          {"loadPreviewPage", &PdfViewWebPlugin::HandleLoadPreviewPageMessage},
          {"releaseSaveInBlockBuffers",
           &PdfViewWebPlugin::HandleReleaseSaveInBlockBuffers},
          {"resetPrintPreviewMode",
           &PdfViewWebPlugin::HandleResetPrintPreviewModeMessage},
          {"rotateClockwise", &PdfViewWebPlugin::HandleRotateClockwiseMessage},
          {"rotateCounterclockwise",
           &PdfViewWebPlugin::HandleRotateCounterclockwiseMessage},
          {"save", &PdfViewWebPlugin::HandleSaveMessage},
          {"saveAttachment", &PdfViewWebPlugin::HandleSaveAttachmentMessage},
          {"selectAll", &PdfViewWebPlugin::HandleSelectAllMessage},
          {"setBackgroundColor",
           &PdfViewWebPlugin::HandleSetBackgroundColorMessage},
          {"setPresentationMode",
           &PdfViewWebPlugin::HandleSetPresentationModeMessage},
          {"setTwoUpView", &PdfViewWebPlugin::HandleSetTwoUpViewMessage},
          {"stopScrolling", &PdfViewWebPlugin::HandleStopScrollingMessage},
          {"viewport", &PdfViewWebPlugin::HandleViewportMessage},
      });

  MessageHandler handler = kMessageHandlers.at(*message.FindString("type"));
  (this->*handler)(message);
}

void PdfViewWebPlugin::HandleDisplayAnnotationsMessage(
    const base::DictValue& message) {
  engine_->DisplayAnnotations(message.FindBool("display").value());
}

void PdfViewWebPlugin::HandleGetNamedDestinationMessage(
    const base::DictValue& message) {
  std::optional<PDFiumEngine::NamedDestination> named_destination =
      engine_->GetNamedDestination(*message.FindString("namedDestination"));

  const int page_number = named_destination.has_value()
                              ? base::checked_cast<int>(named_destination->page)
                              : -1;

  base::DictValue reply = PrepareReplyMessage(message);
  reply.Set("pageNumber", page_number);

  if (named_destination.has_value() && !named_destination->view.empty()) {
    std::ostringstream view_stream;
    view_stream << named_destination->view;
    if (named_destination->xyz_params.empty()) {
      for (unsigned long i = 0; i < named_destination->num_params; ++i) {
        view_stream << "," << named_destination->params[i];
      }
    } else {
      view_stream << "," << named_destination->xyz_params;
    }

    reply.Set("namedDestinationView", view_stream.str());
  }

  client_->PostMessage(std::move(reply));
}

void PdfViewWebPlugin::HandleGetPageBoundingBoxMessage(
    const base::DictValue& message) {
  const int page_index = message.FindInt("page").value();
  PDFiumPage* page = engine_->GetPage(page_index);
  CHECK(page);
  gfx::RectF bounding_box = page->GetBoundingBox();
  gfx::Rect page_bounds = page->rect();

  // Flip the origin from bottom-left to top-left.
  bounding_box.set_y(static_cast<float>(page_bounds.height()) -
                     bounding_box.bottom());

  client_->PostMessage(PrepareReplyMessage(message)
                           .Set("x", bounding_box.x())
                           .Set("y", bounding_box.y())
                           .Set("width", bounding_box.width())
                           .Set("height", bounding_box.height()));
}

void PdfViewWebPlugin::HandleGetPasswordCompleteMessage(
    const base::DictValue& message) {
  DCHECK(password_callback_);
  std::move(password_callback_).Run(*message.FindString("password"));
}

void PdfViewWebPlugin::HandleGetSelectedTextMessage(
    const base::DictValue& message) {
  // Always return unix newlines to JavaScript.
  std::string selected_text;
  base::RemoveChars(engine_->GetSelectedText(), "\r", &selected_text);

  client_->PostMessage(PrepareReplyMessage(message).Set(
      "selectedText", std::move(selected_text)));
}

void PdfViewWebPlugin::HandleGetSaveDataBlockMessage(
    const base::DictValue& message) {
  const std::string& token = *message.FindString("token");
  pdf::mojom::SaveRequestType request_type =
      ParseSaveRequestType(*message.FindString("saveRequestType"));
  uint32_t offset = static_cast<uint32_t>(message.FindInt("offset").value());
  uint32_t block_size =
      static_cast<uint32_t>(message.FindInt("blockSize").value());

  client_->PostMessage(CreateSaveDataBlockMessage(
      token, SaveBlockToBuffer(request_type, offset, block_size)));
}

void PdfViewWebPlugin::HandleGetSuggestedFileName(
    const base::DictValue& message) {
  client_->PostMessage(PrepareReplyMessage(message).Set(
      "fileName", GetFileNameForSaveFromUrl(url_)));
}

void PdfViewWebPlugin::HandleGetThumbnailMessage(
    const base::DictValue& message) {
  const int page_index = message.FindInt("pageIndex").value();
  base::DictValue reply = PrepareReplyMessage(message);

  engine_->RequestThumbnail(
      page_index, device_scale_,
      base::BindOnce(&PdfViewWebPlugin::SendThumbnail,
                     weak_factory_.GetWeakPtr(), std::move(reply), page_index));
}

void PdfViewWebPlugin::HandleHighlightTextFragmentsMessage(
    const base::DictValue& message) {
  const auto* text_fragment_value_list = message.FindList("textFragments");
  std::vector<std::string> text_fragments;
  text_fragments.reserve(text_fragment_value_list->size());
  for (const base::Value& fragment : *text_fragment_value_list) {
    text_fragments.push_back(fragment.GetString());
  }
  if (engine_->FindAndHighlightTextFragments(text_fragments)) {
    engine_->ScrollToFirstTextFragment(/*force_smooth_scroll=*/false);
  }
}

void PdfViewWebPlugin::HandlePrintMessage(const base::DictValue& /*message*/) {
  Print();
}

void PdfViewWebPlugin::HandleRotateClockwiseMessage(
    const base::DictValue& /*message*/) {
  engine_->RotateClockwise();
}

void PdfViewWebPlugin::HandleRotateCounterclockwiseMessage(
    const base::DictValue& /*message*/) {
  engine_->RotateCounterclockwise();
}

void PdfViewWebPlugin::HandleSaveAttachmentMessage(
    const base::DictValue& message) {
  const int index = message.FindInt("attachmentIndex").value();
  CHECK_GE(index, 0);

  const std::vector<DocumentAttachmentInfo>& list =
      engine_->GetDocumentAttachmentInfoList();
  CHECK_LT(static_cast<size_t>(index), list.size());
  CHECK(list[index].is_readable);
  CHECK(IsSaveDataSizeValid(list[index].size_bytes));

  std::vector<uint8_t> data = engine_->GetAttachmentData(index);
  base::Value data_to_save(
      IsSaveDataSizeValid(data.size()) ? data : std::vector<uint8_t>());

  client_->PostMessage(
      PrepareReplyMessage(message).Set("dataToSave", std::move(data_to_save)));
}

void PdfViewWebPlugin::HandleSaveMessage(const base::DictValue& message) {
  const std::string& token = *message.FindString("token");
  pdf::mojom::SaveRequestType request_type =
      ParseSaveRequestType(*message.FindString("saveRequestType"));

  switch (request_type) {
    case pdf::mojom::SaveRequestType::kAnnotation:
#if BUILDFLAG(ENABLE_PDF_INK2)
      // In annotation mode, assume the user will make edits and prefer saving
      // using the plugin data.
      SetPluginCanSave(true);
      SaveToBuffer(request_type, token);
      return;
#else
      NOTREACHED();
#endif  // BUILDFLAG(ENABLE_PDF_INK2)
    case pdf::mojom::SaveRequestType::kOriginal: {
      const bool can_save = plugin_can_save_ || edit_mode_;
      SetPluginCanSave(false);
      SaveToFile(token);
      SetPluginCanSave(can_save);
      return;
    }
    case pdf::mojom::SaveRequestType::kEdited:
      SaveToBuffer(request_type, token);
      return;
    case pdf::mojom::SaveRequestType::kSearchified:
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
      CHECK(base::FeatureList::IsEnabled(
          chrome_pdf::features::kPdfSearchifySave));
      // TODO(crbug.com/382610226): If engine has searchified text, ensure all
      // pages are searchified and then save.
      SaveToBuffer(request_type, token);
      return;
#else
      // PDF Searchify is not expected to be triggered when ScreenAI service is
      // not enabled.
      NOTREACHED();
#endif
  }
  NOTREACHED();
}

void PdfViewWebPlugin::HandleSelectAllMessage(
    const base::DictValue& /*message*/) {
  engine_->SelectAll();
}

void PdfViewWebPlugin::HandleSetBackgroundColorMessage(
    const base::DictValue& message) {
  background_color_ =
      base::checked_cast<SkColor>(message.FindDouble("color").value());
}

void PdfViewWebPlugin::HandleSetPresentationModeMessage(
    const base::DictValue& message) {
  const bool presentation_mode =
      message.FindBool("enablePresentationMode").value();
  engine_->SetReadOnly(presentation_mode);
  if (presentation_mode) {
    cursor_ = ui::mojom::CursorType::kPointer;
  }
}

void PdfViewWebPlugin::HandleSetTwoUpViewMessage(
    const base::DictValue& message) {
  engine_->SetDocumentLayout(message.FindBool("enableTwoUpView").value()
                                 ? DocumentLayout::PageSpread::kTwoUpOdd
                                 : DocumentLayout::PageSpread::kOneUp);
}

void PdfViewWebPlugin::HandleStopScrollingMessage(
    const base::DictValue& /*message*/) {
  stop_scrolling_ = true;
}

void PdfViewWebPlugin::HandleViewportMessage(const base::DictValue& message) {
  const base::DictValue* layout_options_value =
      message.FindDict("layoutOptions");
  if (layout_options_value) {
    DocumentLayout::Options layout_options;
    layout_options.FromValue(*layout_options_value);

    ui_direction_ = layout_options.direction();

    // TODO(crbug.com/40652841): Eliminate need to get document size from here.
    document_size_ = engine_->ApplyDocumentLayout(layout_options);

    OnGeometryChanged(zoom_, device_scale_);
    if (!document_size_.IsEmpty()) {
      paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
    }

    // Send 100% loading progress only after initial layout negotiated.
    if (last_progress_sent_ < 100 &&
        document_load_state_ == DocumentLoadState::kComplete) {
      SendLoadingProgress(/*percentage=*/100);
    }
  }

  gfx::Vector2dF scroll_offset(*message.FindDouble("xOffset"),
                               *message.FindDouble("yOffset"));
  double new_zoom = *message.FindDouble("zoom");
  const PinchPhase pinch_phase =
      static_cast<PinchPhase>(*message.FindInt("pinchPhase"));

  received_viewport_message_ = true;
  stop_scrolling_ = false;
  const double zoom_ratio = new_zoom / zoom_;

  if (pinch_phase == PinchPhase::kStart) {
    scroll_offset_at_last_raster_ = scroll_offset;
    last_bitmap_smaller_ = false;
    needs_reraster_ = false;
    return;
  }

  // When zooming in, we set a layer transform to avoid unneeded rerasters.
  // Also, if we're zooming out and the last time we rerastered was when
  // we were even further zoomed out (i.e. we pinch zoomed in and are now
  // pinch zooming back out in the same gesture), we update the layer
  // transform instead of rerastering.
  if (pinch_phase == PinchPhase::kUpdateZoomIn ||
      (pinch_phase == PinchPhase::kUpdateZoomOut && zoom_ratio > 1.0)) {
    // Get the coordinates of the center of the pinch gesture.
    const double pinch_x = *message.FindDouble("pinchX");
    const double pinch_y = *message.FindDouble("pinchY");
    gfx::Point pinch_center(pinch_x, pinch_y);

    // Get the pinch vector which represents the panning caused by the change in
    // pinch center between the start and the end of the gesture.
    const double pinch_vector_x = *message.FindDouble("pinchVectorX");
    const double pinch_vector_y = *message.FindDouble("pinchVectorY");
    gfx::Vector2d pinch_vector =
        gfx::Vector2d(pinch_vector_x * zoom_ratio, pinch_vector_y * zoom_ratio);

    gfx::Vector2d scroll_delta;
    // If the rendered document doesn't fill the display area we will
    // use `paint_offset` to anchor the paint vertically into the same place.
    // We use the scroll bars instead of the pinch vector to get the actual
    // position on screen of the paint.
    gfx::Vector2d paint_offset;

    if (plugin_rect_.width() > GetDocumentPixelWidth() * zoom_ratio) {
      // We want to keep the paint in the middle but it must stay in the same
      // position relative to the scroll bars.
      paint_offset = gfx::Vector2d(0, (1 - zoom_ratio) * pinch_center.y());
      scroll_delta = gfx::Vector2d(
          0,
          (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));

      pinch_vector = gfx::Vector2d();
      last_bitmap_smaller_ = true;
    } else if (last_bitmap_smaller_) {
      // When the document width covers the display area's width, we will anchor
      // the scroll bars disregarding where the actual pinch certer is.
      pinch_center = gfx::Point((plugin_rect_.width() / device_scale_) / 2,
                                (plugin_rect_.height() / device_scale_) / 2);
      const double zoom_when_doc_covers_plugin_width =
          zoom_ * plugin_rect_.width() / GetDocumentPixelWidth();
      paint_offset = gfx::Vector2d(
          (1 - new_zoom / zoom_when_doc_covers_plugin_width) * pinch_center.x(),
          (1 - zoom_ratio) * pinch_center.y());
      pinch_vector = gfx::Vector2d();
      scroll_delta = gfx::Vector2d(
          (scroll_offset.x() - scroll_offset_at_last_raster_.x() * zoom_ratio),
          (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
    }

    paint_manager_.SetTransform(zoom_ratio, pinch_center,
                                pinch_vector + paint_offset + scroll_delta,
                                true);
    needs_reraster_ = false;
    return;
  }

  if (pinch_phase == PinchPhase::kUpdateZoomOut ||
      pinch_phase == PinchPhase::kEnd) {
    // We reraster on pinch zoom out in order to solve the invalid regions
    // that appear after zooming out.
    // On pinch end the scale is again 1.f and we request a reraster
    // in the new position.
    paint_manager_.ClearTransform();
    last_bitmap_smaller_ = false;
    needs_reraster_ = true;

    // If we're rerastering due to zooming out, we need to update the scroll
    // offset for the last raster, in case the user continues the gesture by
    // zooming in.
    scroll_offset_at_last_raster_ = scroll_offset;
  }

  // Bound the input parameters.
  new_zoom = std::max(kMinZoom, new_zoom);
  DCHECK(message.FindBool("userInitiated").has_value());

  double old_zoom = zoom_;
  zoom_ = new_zoom;

  OnGeometryChanged(old_zoom, device_scale_);
  if (!document_size_.IsEmpty()) {
    paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
  }

  UpdateScroll(GetScrollPositionFromOffset(scroll_offset));
}

void PdfViewWebPlugin::SaveToBuffer(pdf::mojom::SaveRequestType request_type,
                                    const std::string& token) {
  CHECK(request_type == pdf::mojom::SaveRequestType::kAnnotation ||
        request_type == pdf::mojom::SaveRequestType::kEdited ||
        request_type == pdf::mojom::SaveRequestType::kSearchified);

  engine_->KillFormFocus();

#if BUILDFLAG(ENABLE_PDF_INK2)
  if (request_type == pdf::mojom::SaveRequestType::kAnnotation && ink_module_) {
    ink_module_->RecordMetricsOnSave();
  }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)

  auto message = base::DictValue()
                     .Set("type", "saveData")
                     .Set("token", token)
                     .Set("fileName", GetFileNameForSaveFromUrl(url_));

  // Expose `edit_mode_` state for integration testing.
  message.Set("editModeForTesting", edit_mode_);

  base::Value data_to_save;

  bool use_save_data = edit_mode_;
#if BUILDFLAG(ENABLE_PDF_INK2)
  use_save_data |= !!ink_module_;
#endif  // BUILDFLAG(ENABLE_PDF_INK2)

#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
  use_save_data |= (request_type == pdf::mojom::SaveRequestType::kSearchified);
#endif

  if (use_save_data) {
    base::Value::BlobStorage data = engine_->GetSaveData();
    if (IsSaveDataSizeValid(data.size())) {
      data_to_save = base::Value(std::move(data));
    }
  } else {
    // TODO(crbug.com/425604529): Remove `use_save_data`?
    NOTREACHED();
  }

  message.Set("dataToSave", std::move(data_to_save));
  client_->PostMessage(std::move(message));
}

uint32_t PdfViewWebPlugin::GetOriginalFileSize() {
  const uint32_t size = engine_->GetLoadedByteSize();
  // This function does not handle files larger than INT_MAX.
  return size <= static_cast<uint32_t>(INT_MAX) ? size : 0;
}

std::vector<uint8_t> PdfViewWebPlugin::GetOriginalFileData(
    uint32_t offset,
    uint32_t block_size) {
  std::vector<uint8_t> block(block_size);
  if (!engine_->ReadLoadedBytes(offset, block)) {
    block.resize(0);
  }
  return block;
}

void PdfViewWebPlugin::PopulateBufferWithModifiedFileData(
    std::vector<uint8_t>& buffer) {
  buffer = engine_->GetSaveData();
  // This function does not handle files larger than INT_MAX.
  if (buffer.size() > static_cast<uint32_t>(INT_MAX)) {
    ReleaseBuffer(buffer);
  }
}

std::vector<uint8_t> PdfViewWebPlugin::GetModifiedFileDataFromBuffer(
    base::span<const uint8_t> buffer,
    uint32_t offset,
    uint32_t block_size) {
  auto data_span = buffer.subspan(offset, block_size);
  return {data_span.begin(), data_span.end()};
}

uint32_t PdfViewWebPlugin::VerifyParamsAndGetSaveBlockSize(
    uint32_t total_file_size,
    uint32_t offset,
    uint32_t block_size) {
  if (block_size) {
    // Block size should be less than max threshold.
    CHECK_LE(block_size, max_save_buffer_size_);
  } else {
    // `block_size` is allowed to be 0 only when offset is 0 since the caller
    // may not know the total file size at that point.
    CHECK(!offset);
  }
  CHECK_LT(offset, total_file_size);
  if (block_size) {
    CHECK_LE(block_size, total_file_size - offset);
  } else {
    block_size = std::min(max_save_buffer_size_, total_file_size);
  }
  return block_size;
}

PdfViewWebPlugin::SaveDataBlock PdfViewWebPlugin::SaveBlockToBuffer(
    pdf::mojom::SaveRequestType request_type,
    uint32_t offset,
    uint32_t block_size) {
  engine_->KillFormFocus();

  SaveDataBlock result;
  if (request_type == pdf::mojom::SaveRequestType::kOriginal) {
    result.total_file_size = GetOriginalFileSize();
    if (result.total_file_size) {
      block_size = VerifyParamsAndGetSaveBlockSize(result.total_file_size,
                                                   offset, block_size);
      result.block = GetOriginalFileData(offset, block_size);
    }
    return result;
  }

  if (offset == 0) {
    PopulateBufferWithModifiedFileData(save_data_buffer_);
#if BUILDFLAG(ENABLE_PDF_INK2)
    if (request_type == pdf::mojom::SaveRequestType::kAnnotation &&
        ink_module_) {
      ink_module_->RecordMetricsOnSave();
    }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)
  } else {
    CHECK(save_data_buffer_.size());
  }
  result.total_file_size = static_cast<uint32_t>(save_data_buffer_.size());
  if (result.total_file_size) {
    block_size = VerifyParamsAndGetSaveBlockSize(result.total_file_size, offset,
                                                 block_size);
    result.block =
        GetModifiedFileDataFromBuffer(save_data_buffer_, offset, block_size);
    // Drop the buffer if everything is returned.
    if (offset + block_size == result.total_file_size) {
      ReleaseBuffer(save_data_buffer_);
    }
  }

  return result;
}

void PdfViewWebPlugin::SaveToFile(const std::string& token) {
  engine_->KillFormFocus();

  client_->PostMessage(
      base::DictValue().Set("type", "consumeSaveToken").Set("token", token));

  pdf_host_->SavePdf();
}

void PdfViewWebPlugin::SetPluginCanSave(bool can_save) {
  if (plugin_can_save_ == can_save) {
    return;
  }

  plugin_can_save_ = can_save;
  pdf_host_->SetPluginCanSave(can_save);
}

void PdfViewWebPlugin::InvalidatePluginContainer() {
  client_->Invalidate();
}

void PdfViewWebPlugin::OnPaint(const std::vector<gfx::Rect>& paint_rects,
                               std::vector<PaintReadyRect>& ready,
                               std::vector<gfx::Rect>& pending) {
  base::AutoReset<bool> auto_reset_in_paint(&in_paint_, true);
  DoPaint(paint_rects, ready, pending);
}

gfx::PointF PdfViewWebPlugin::GetScrollPositionFromOffset(
    const gfx::Vector2dF& scroll_offset) const {
  gfx::PointF scroll_origin;

  // TODO(crbug.com/40726602): Right-to-left scrolling currently is not
  // compatible with the PDF viewer's sticky "scroller" element.
  if (ui_direction_ == base::i18n::RIGHT_TO_LEFT && IsPrintPreview()) {
    scroll_origin.set_x(
        std::max(document_size_.width() * static_cast<float>(zoom_) -
                     plugin_dip_size_.width(),
                 0.0f));
  }

  return scroll_origin + scroll_offset;
}

void PdfViewWebPlugin::DoPaint(const std::vector<gfx::Rect>& paint_rects,
                               std::vector<PaintReadyRect>& ready,
                               std::vector<gfx::Rect>& pending) {
  if (image_data_.drawsNothing()) {
    DCHECK(plugin_rect_.IsEmpty());
    return;
  }

  PrepareForFirstPaint(ready);

  if (!received_viewport_message_ || !needs_reraster_) {
    return;
  }

  engine_->PrePaint();

  std::vector<gfx::Rect> ready_rects;
  for (const gfx::Rect& paint_rect : paint_rects) {
    // Intersect with plugin area since there could be pending invalidates from
    // when the plugin area was larger.
    gfx::Rect rect =
        gfx::IntersectRects(paint_rect, gfx::Rect(plugin_rect_.size()));
    if (rect.IsEmpty()) {
      continue;
    }

    // Paint the rendering of the PDF document.
    gfx::Rect pdf_rect = gfx::IntersectRects(rect, available_area_);
    if (!pdf_rect.IsEmpty()) {
      pdf_rect.Offset(-available_area_.x(), 0);

      std::vector<gfx::Rect> pdf_ready;
      std::vector<gfx::Rect> pdf_pending;
      engine_->Paint(pdf_rect, image_data_, pdf_ready, pdf_pending);
      for (gfx::Rect& ready_rect : pdf_ready) {
        ready_rect.Offset(available_area_.OffsetFromOrigin());
        ready_rects.push_back(ready_rect);
      }
      for (gfx::Rect& pending_rect : pdf_pending) {
        pending_rect.Offset(available_area_.OffsetFromOrigin());
        pending.push_back(pending_rect);
      }
    }

    // Ensure the region above the first page (if any) is filled;
    const int32_t first_page_ypos = 0 == engine_->GetNumberOfPages()
                                        ? 0
                                        : engine_->GetPageScreenRect(0).y();
    if (rect.y() < first_page_ypos) {
      gfx::Rect region = gfx::IntersectRects(
          rect, gfx::Rect(gfx::Size(plugin_rect_.width(), first_page_ypos)));
      image_data_.erase(GetBackgroundColor(), gfx::RectToSkIRect(region));
      ready_rects.push_back(region);
    }

    // Ensure the background parts are filled.
    for (const BackgroundPart& background_part : background_parts_) {
      gfx::Rect intersection =
          gfx::IntersectRects(background_part.location, rect);
      if (!intersection.IsEmpty()) {
        image_data_.erase(background_part.color,
                          gfx::RectToSkIRect(intersection));
        ready_rects.push_back(intersection);
      }
    }
  }

  engine_->PostPaint();

  if (base::FeatureList::IsEnabled(features::kPdfBufferedPaintManager)) {
    for (const gfx::Rect& ready_rect : ready_rects) {
      ready.emplace_back(ready_rect, nullptr);
    }
  } else {
    // TODO(crbug.com/40203030): Write pixels directly to the `SkSurface` in
    // `PaintManager`, rather than using an intermediate `SkBitmap` and
    // `SkImage`.
    sk_sp<SkImage> painted_image = image_data_.asImage();
    for (const gfx::Rect& ready_rect : ready_rects) {
      ready.emplace_back(ready_rect, painted_image);
    }
  }

  InvalidateAfterPaintDone();
}

void PdfViewWebPlugin::PrepareForFirstPaint(
    std::vector<PaintReadyRect>& ready) {
  if (!first_paint_) {
    return;
  }

  // Fill the image data buffer with the background color.
  first_paint_ = false;
  image_data_.eraseColor(background_color_);
  if (base::FeatureList::IsEnabled(features::kPdfBufferedPaintManager)) {
    ready.emplace_back(gfx::SkIRectToRect(image_data_.bounds()), nullptr,
                       /*flush_now=*/true);
  } else {
    ready.emplace_back(gfx::SkIRectToRect(image_data_.bounds()),
                       image_data_.asImage(), /*flush_now=*/true);
  }
}

void PdfViewWebPlugin::OnGeometryChanged(double old_zoom,
                                         float old_device_scale) {
  RecalculateAreas(old_zoom, old_device_scale);

  if (accessibility_state_ == AccessibilityState::kLoaded) {
    PrepareAndSetAccessibilityViewportInfo();
  }

#if BUILDFLAG(ENABLE_PDF_INK2)
  if (ink_module_) {
    ink_module_->OnGeometryChanged();
  }
#endif
}

void PdfViewWebPlugin::RecalculateAreas(double old_zoom,
                                        float old_device_scale) {
  if (zoom_ != old_zoom || device_scale_ != old_device_scale) {
    engine_->ZoomUpdated(zoom_ * device_scale_);
  }

  available_area_ = gfx::Rect(plugin_rect_.size());
  int doc_width = GetDocumentPixelWidth();
  if (doc_width < available_area_.width()) {
    // Center the document horizontally inside the plugin rectangle.
    available_area_.Offset((plugin_rect_.width() - doc_width) / 2, 0);
    available_area_.set_width(doc_width);
  }

  // The distance between top of the plugin and the bottom of the document in
  // pixels.
  int bottom_of_document = GetDocumentPixelHeight();
  if (bottom_of_document < plugin_rect_.height()) {
    available_area_.set_height(bottom_of_document);
  }

  CalculateBackgroundParts();

  engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
  engine_->PluginSizeUpdated(available_area_.size());
}

void PdfViewWebPlugin::CalculateBackgroundParts() {
  background_parts_.clear();
  int left_width = available_area_.x();
  int right_start = available_area_.right();
  int right_width = std::abs(plugin_rect_.width() - available_area_.right());
  int bottom = std::min(available_area_.bottom(), plugin_rect_.height());

  // Note: we assume the display of the PDF document is always centered
  // horizontally, but not necessarily centered vertically.
  // Add the left rectangle.
  BackgroundPart part = {gfx::Rect(left_width, bottom), GetBackgroundColor()};
  if (!part.location.IsEmpty()) {
    background_parts_.push_back(part);
  }

  // Add the right rectangle.
  part.location = gfx::Rect(right_start, 0, right_width, bottom);
  if (!part.location.IsEmpty()) {
    background_parts_.push_back(part);
  }

  // Add the bottom rectangle.
  part.location = gfx::Rect(0, bottom, plugin_rect_.width(),
                            plugin_rect_.height() - bottom);
  if (!part.location.IsEmpty()) {
    background_parts_.push_back(part);
  }
}

int PdfViewWebPlugin::GetDocumentPixelWidth() const {
  return static_cast<int>(
      std::ceil(document_size_.width() * zoom_ * device_scale_));
}

int PdfViewWebPlugin::GetDocumentPixelHeight() const {
  return static_cast<int>(
      std::ceil(document_size_.height() * zoom_ * device_scale_));
}

void PdfViewWebPlugin::InvalidateAfterPaintDone() {
  if (deferred_invalidates_.empty()) {
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&PdfViewWebPlugin::ClearDeferredInvalidates,
                                weak_factory_.GetWeakPtr()));
}

void PdfViewWebPlugin::ClearDeferredInvalidates() {
  DCHECK(!in_paint_);
  for (const gfx::Rect& rect : deferred_invalidates_) {
    Invalidate(rect);
  }
  deferred_invalidates_.clear();
}

SkBitmap* PdfViewWebPlugin::InstallBuffer(SkImageInfo image_info,
                                          base::span<uint8_t> data) {
  CHECK_GE(data.size(), image_info.computeMinByteSize());
  image_data_.installPixels(image_info, data.data(), image_info.minRowBytes());
  first_paint_ = true;
  return &image_data_;
}

void PdfViewWebPlugin::UpdateSnapshot(sk_sp<SkImage> snapshot) {
  // Every time something changes (e.g. scale or scroll position),
  // `UpdateSnapshot()` is called, so the snapshot is effectively used only
  // once. Make it "no-cache" so that the old snapshots are not cached
  // downstream.
  //
  // Otherwise, for instance when scrolling, all the previous snapshots end up
  // accumulating in the (for the GPU path) GpuImageDecodeCache, and then in the
  // service transfer cache. The size of the service transfer cache is bounded,
  // so on desktop this "only" causes a 256MiB memory spike, but it's completely
  // wasted memory nonetheless.
  snapshot_ =
      cc::PaintImageBuilder::WithDefault()
          .set_image(std::move(snapshot), cc::PaintImage::GetNextContentId())
          .set_id(cc::PaintImage::GetNextId())
          .set_no_cache(true)
          .TakePaintImage();

#if BUILDFLAG(ENABLE_PDF_INK2)
  // `paint_manager_` updates the snapshot after it has completed painting,
  // which uses `engine_` in `DoPaint()`.  Any newly added Ink stroke will now
  // be applied in the snapshot.
  snapshot_needs_update_for_ink_input_ = false;
#endif

  if (!plugin_rect_.IsEmpty()) {
    InvalidatePluginContainer();
  }
}

void PdfViewWebPlugin::UpdateScaledValues() {
  total_translate_ = snapshot_translate_;

  if (viewport_to_dip_scale_ != 1.0f) {
    total_translate_.Scale(1.0f / viewport_to_dip_scale_);
  }
}

void PdfViewWebPlugin::UpdateScale(float scale) {
  CHECK_GT(scale, 0.0f);
  viewport_to_dip_scale_ = scale;
  UpdateScaledValues();
}

void PdfViewWebPlugin::UpdateLayerTransform(float scale,
                                            const gfx::Vector2dF& translate) {
  snapshot_translate_ = translate;
  snapshot_scale_ = scale;
  UpdateScaledValues();
}

void PdfViewWebPlugin::EnableAccessibility() {
  if (accessibility_state_ == AccessibilityState::kLoaded) {
    return;
  }

  LoadOrReloadAccessibility();
}

void PdfViewWebPlugin::HandleAccessibilityAction(
    const AccessibilityActionData& action_data) {
  engine_->HandleAccessibilityAction(action_data);
}

void PdfViewWebPlugin::LoadOrReloadAccessibility() {
  if (accessibility_state_ == AccessibilityState::kOff) {
    accessibility_state_ = AccessibilityState::kPending;
  }

  if (document_load_state_ == DocumentLoadState::kComplete) {
    LoadAccessibility();
  }
}

void PdfViewWebPlugin::OnViewportChanged(
    const gfx::Rect& new_plugin_rect_in_css_pixel,
    float new_device_scale) {
  DCHECK_GT(new_device_scale, 0.0f);

  css_plugin_rect_ = new_plugin_rect_in_css_pixel;

  if (new_device_scale == device_scale_ &&
      new_plugin_rect_in_css_pixel == plugin_rect_) {
    return;
  }

  const float old_device_scale = device_scale_;
  device_scale_ = new_device_scale;
  plugin_rect_ = new_plugin_rect_in_css_pixel;

  // TODO(crbug.com/40791703): We should try to avoid the downscaling in this
  // calculation, perhaps by migrating off `plugin_dip_size_`.
  plugin_dip_size_ = gfx::ScaleToEnclosingRect(new_plugin_rect_in_css_pixel,
                                               1.0f / new_device_scale)
                         .size();

  SkAlphaType alpha_type =
      UseSkiaPremultipliedAlpha() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
  paint_manager_.SetSize(plugin_rect_.size(), device_scale_, alpha_type);
  // In the PdfBufferedPaintManager experiment, all these calculations and
  // allocations are done in the PaintManager.
  if (!base::FeatureList::IsEnabled(features::kPdfBufferedPaintManager)) {
    // Initialize the image data buffer if the context size changes.
    const gfx::Size old_image_size =
        gfx::SkISizeToSize(image_data_.dimensions());
    const gfx::Size new_image_size =
        PaintManager::GetNewContextSize(old_image_size, plugin_rect_.size());
    if (new_image_size != old_image_size) {
      // Ignore the result. If the allocation fails, the image data buffer
      // will be empty and the code below will handle that.
      (void)image_data_.tryAllocPixels(SkImageInfo::MakeN32(
          new_image_size.width(), new_image_size.height(), alpha_type));
      first_paint_ = true;
    }
  }

  // Skip updating the geometry if the new image data buffer is empty.
  if (image_data_.drawsNothing()) {
    return;
  }

  OnGeometryChanged(zoom_, old_device_scale);
}

bool PdfViewWebPlugin::SelectAll() {
  engine_->SelectAll();
  return true;
}

bool PdfViewWebPlugin::Cut() {
  if (!HasSelection() || !CanEditText()) {
    return false;
  }

  engine_->ReplaceSelection("");
  return true;
}

bool PdfViewWebPlugin::Paste(const blink::WebString& value) {
  if (!CanEditText()) {
    return false;
  }

  engine_->ReplaceSelection(value.Utf8());
  return true;
}

bool PdfViewWebPlugin::Undo() {
  if (!CanUndo()) {
    return false;
  }

  engine_->Undo();
  return true;
}

bool PdfViewWebPlugin::Redo() {
  if (!CanRedo()) {
    return false;
  }

  engine_->Redo();
  return true;
}

bool PdfViewWebPlugin::SetFocusedFormTextDirection(
    base::i18n::TextDirection direction) {
  if (!CanEditText()) {
    return false;
  }

  return engine_->SetFocusedFormTextDirection(direction);
}

bool PdfViewWebPlugin::HandleWebInputEvent(const blink::WebInputEvent& event) {
  // Ignore user input in read-only mode.
  if (engine_->IsReadOnly()) {
    return false;
  }

  // `engine_` expects input events in device coordinates.
  float viewport_to_device_scale = viewport_to_dip_scale_ * device_scale_;
  std::unique_ptr<blink::WebInputEvent> transformed_event =
      ui::TranslateAndScaleWebInputEvent(
          event,
          gfx::Vector2dF(-available_area_.x() / viewport_to_device_scale, 0),
          viewport_to_device_scale);

  const blink::WebInputEvent& event_to_handle =
      transformed_event ? *transformed_event : event;

#if BUILDFLAG(ENABLE_PDF_INK2)
  if (ink_module_ && ink_module_->HandleInputEvent(event_to_handle)) {
    return true;
  }

  if (IsInAnnotationMode()) {
    // When in annotation mode, only handle ink input events.
    return false;
  }
#endif

  if (engine_->HandleInputEvent(event_to_handle)) {
    return true;
  }

  // Middle click is used for scrolling and is handled by the container page.
  if (blink::WebInputEvent::IsMouseEventType(event_to_handle.GetType()) &&
      static_cast<const blink::WebMouseEvent&>(event_to_handle).button ==
          blink::WebPointerProperties::Button::kMiddle) {
    return false;
  }

  // Return true for unhandled clicks so the plugin takes focus.
  return event_to_handle.GetType() == blink::WebInputEvent::Type::kMouseDown;
}

void PdfViewWebPlugin::HandleImeCommit(const blink::WebString& text) {
  if (text.IsEmpty()) {
    return;
  }

  std::u16string text16 = text.Utf16();
  composition_text_.Reset();

  size_t i = 0;
  for (base::i18n::UTF16CharIterator iterator(text16); iterator.Advance();) {
    blink::WebKeyboardEvent char_event(blink::WebInputEvent::Type::kChar,
                                       blink::WebInputEvent::kNoModifiers,
                                       ui::EventTimeForNow());
    char_event.windows_key_code = text16[i];
    char_event.native_key_code = text16[i];

    for (const size_t char_start = i; i < iterator.array_pos(); ++i) {
      char_event.text[i - char_start] = text16[i];
      char_event.unmodified_text[i - char_start] = text16[i];
    }

    blink::WebCoalescedInputEvent input_event(char_event, ui::LatencyInfo());
    ui::Cursor dummy_cursor_info;
    HandleInputEvent(input_event, &dummy_cursor_info);
  }
}

void PdfViewWebPlugin::OnInvokePrintDialog() {
  client_->Print();
}

void PdfViewWebPlugin::ResetRecentlySentFindUpdate() {
  recently_sent_find_update_ = false;
}

void PdfViewWebPlugin::RecordDocumentMetrics() {
  if (!metrics_handler_) {
    return;
  }

  metrics_handler_->RecordDocumentMetrics(engine_->GetDocumentMetadata());

#if BUILDFLAG(ENABLE_PDF_INK2)
  // `metrics_handler_` is only initialized when not in Print Preview, so the
  // V2 ink annotations load metric will not count Print Preview loads.
  if (ink_module_) {
    // Use a timeout limit of 100ms, which will capture over 90 percent of PDFs
    // without increasing the PDF load time a significant amount.
    PDFiumEngine::InkIdentifiers ink_identifiers =
        engine_->ScanForInkAnnotations(base::Milliseconds(100));
    RecordPdfLoadedWithV2InkAnnotations(ink_identifiers.v2_ink_path);
    RecordPdfLoadedWithInkTextAnnotations(ink_identifiers.ink_text_annotations);
  }
#endif  // BUILDFLAG(ENABLE_PDF_INK2)
}

void PdfViewWebPlugin::SendAttachments() {
  const std::vector<DocumentAttachmentInfo>& attachment_infos =
      engine_->GetDocumentAttachmentInfoList();
  if (attachment_infos.empty()) {
    return;
  }

  base::ListValue attachments;
  attachments.reserve(attachment_infos.size());
  for (const DocumentAttachmentInfo& attachment_info : attachment_infos) {
    // Send `size` as -1 to indicate that the attachment is too large to be
    // downloaded.
    const int size = attachment_info.size_bytes <= kMaximumSavedFileSize
                         ? static_cast<int>(attachment_info.size_bytes)
                         : -1;

    attachments.Append(base::DictValue()
                           .Set("name", attachment_info.name)
                           .Set("size", size)
                           .Set("readable", attachment_info.is_readable));
  }

  client_->PostMessage(base::DictValue()
                           .Set("type", "attachments")
                           .Set("attachmentsData", std::move(attachments)));
}

void PdfViewWebPlugin::SendBookmarks() {
  base::ListValue bookmarks = engine_->GetBookmarks();
  if (bookmarks.empty()) {
    return;
  }

  client_->PostMessage(base::DictValue()
                           .Set("type", "bookmarks")
                           .Set("bookmarksData", std::move(bookmarks)));
}

void PdfViewWebPlugin::SendExecutedEditCommand(std::string_view edit_command) {
  client_->PostMessage(base::DictValue()
                           .Set("type", "executedEditCommand")
                           .Set("editCommand", edit_command));
}

void PdfViewWebPlugin::SendStartedFindInPage() {
  client_->PostMessage(base::DictValue().Set("type", "startedFindInPage"));
}

void PdfViewWebPlugin::SendMetadata() {
  base::DictValue metadata;
  const DocumentMetadata& document_metadata = engine_->GetDocumentMetadata();

  const std::string version = FormatPdfVersion(document_metadata.version);
  if (!version.empty()) {
    metadata.Set("version", version);
  }

  metadata.Set("fileSize",
               ui::FormatBytes(base::ByteSize(base::checked_cast<uint64_t>(
                   document_metadata.size_bytes))));

  metadata.Set("linearized", document_metadata.linearized);

  std::string title = document_metadata.title;
  if (title.empty()) {
    title = engine_->GetFileNameFromContentDisposition();
  }
  if (!title.empty()) {
    metadata.Set("title", std::move(title));
  }

  if (!document_metadata.author.empty()) {
    metadata.Set("author", document_metadata.author);
  }

  if (!document_metadata.subject.empty()) {
    metadata.Set("subject", document_metadata.subject);
  }

  if (!document_metadata.keywords.empty()) {
    metadata.Set("keywords", document_metadata.keywords);
  }

  if (!document_metadata.creator.empty()) {
    metadata.Set("creator", document_metadata.creator);
  }

  if (!document_metadata.producer.empty()) {
    metadata.Set("producer", document_metadata.producer);
  }

  if (!document_metadata.creation_date.is_null()) {
    metadata.Set("creationDate", base::TimeFormatShortDateAndTime(
                                     document_metadata.creation_date));
  }

  if (!document_metadata.mod_date.is_null()) {
    metadata.Set("modDate",
                 base::TimeFormatShortDateAndTime(document_metadata.mod_date));
  }

  metadata.Set("pageSize", FormatPageSize(engine_->GetUniformPageSizePoints()));

  metadata.Set("canSerializeDocument",
               IsSaveDataSizeValid(engine_->GetLoadedByteSize()));

  client_->PostMessage(base::DictValue()
                           .Set("type", "metadata")
                           .Set("metadataData", std::move(metadata)));
}

void PdfViewWebPlugin::SendLoadingProgress(double percentage) {
  DCHECK(percentage == -1 || (percentage >= 0 && percentage <= 100));
  last_progress_sent_ = percentage;

  client_->PostMessage(base::DictValue()
                           .Set("type", "loadProgress")
                           .Set("progress", percentage));
}

void PdfViewWebPlugin::HandleReleaseSaveInBlockBuffers(
    const base::DictValue& /*message*/) {
  ReleaseBuffer(save_data_buffer_);
}

void PdfViewWebPlugin::HandleResetPrintPreviewModeMessage(
    const base::DictValue& message) {
  const std::string& url = *message.FindString("url");
  bool is_grayscale = message.FindBool("grayscale").value();
  int print_preview_page_count = message.FindInt("pageCount").value();

  // For security reasons, crash if `url` is not for Print Preview.
  CHECK(IsPrintPreview());
  CHECK(IsPrintPreviewUrl(url));

  DCHECK_GE(print_preview_page_count, 0);

  int page_index = ExtractPrintPreviewPageIndex(url);
  if (IsPreviewingPDF(print_preview_page_count)) {
    DCHECK_EQ(page_index, kCompletePDFIndex);
  } else {
    DCHECK_GE(page_index, 0);
  }

  print_preview_page_count_ = print_preview_page_count;
  print_preview_loaded_page_count_ = 0;
  url_ = url;
  preview_pages_info_ = base::queue<PreviewPageInfo>();
  preview_document_load_state_ = DocumentLoadState::kComplete;
  document_load_state_ = DocumentLoadState::kLoading;
  last_progress_sent_ = 0;
  LoadUrl(url_, base::BindOnce(&PdfViewWebPlugin::DidOpen,
                               weak_factory_.GetWeakPtr()));
  preview_engine_.reset();

  // TODO(crbug.com/40193305): Figure out a more consistent way to preserve
  // engine settings across a Print Preview reset.
  engine_ = client_->CreateEngine(
      this, PDFiumFormFiller::ScriptOption::kNoJavaScript);
  engine_->ZoomUpdated(zoom_ * device_scale_);
  engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
  engine_->PluginSizeUpdated(available_area_.size());
  engine_->SetGrayscale(is_grayscale);

  paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
}

void PdfViewWebPlugin::HandleLoadPreviewPageMessage(
    const base::DictValue& message) {
  const std::string& url = *message.FindString("url");
  int dest_page_index = message.FindInt("index").value();

  // For security reasons, crash if `url` is not for Print Preview.
  CHECK(IsPrintPreview());
  CHECK(IsPrintPreviewUrl(url));

  DCHECK_GE(dest_page_index, 0);
  DCHECK_LT(dest_page_index, print_preview_page_count_);

  // Print Preview JS will send the loadPreviewPage message for every page,
  // including the first page in the print preview, which has already been
  // loaded when handing the resetPrintPreviewMode message. Just ignore it.
  if (dest_page_index == 0) {
    return;
  }

  int src_page_index = ExtractPrintPreviewPageIndex(url);
  DCHECK_GE(src_page_index, 0);

  preview_pages_info_.push({.url = url, .dest_page_index = dest_page_index});
  LoadAvailablePreviewPage();
}

void PdfViewWebPlugin::LoadAvailablePreviewPage() {
  if (preview_pages_info_.empty() ||
      document_load_state_ != DocumentLoadState::kComplete ||
      preview_document_load_state_ == DocumentLoadState::kLoading) {
    return;
  }

  preview_document_load_state_ = DocumentLoadState::kLoading;
  const std::string& url = preview_pages_info_.front().url;

  // Note that `last_progress_sent_` is not reset for preview page loads.
  LoadUrl(url, base::BindOnce(&PdfViewWebPlugin::DidOpenPreview,
                              weak_factory_.GetWeakPtr()));
}

void PdfViewWebPlugin::DidOpenPreview(std::unique_ptr<UrlLoader> loader,
                                      Result result) {
  DCHECK_EQ(result, Result::kSuccess);

  // `preview_engine_` holds a `raw_ptr` to `preview_client_`.
  // We need to explicitly destroy it before clobbering
  // `preview_client_` to dodge lifetime issues.
  preview_engine_.reset();

  preview_client_ = std::make_unique<PreviewModeClient>(this);
  preview_engine_ = client_->CreateEngine(
      preview_client_.get(), PDFiumFormFiller::ScriptOption::kNoJavaScript);
  preview_engine_->PluginSizeUpdated({});
  preview_engine_->HandleDocumentLoad(std::move(loader), url_);
}

void PdfViewWebPlugin::PreviewDocumentLoadComplete() {
  if (preview_document_load_state_ != DocumentLoadState::kLoading ||
      preview_pages_info_.empty()) {
    return;
  }

  preview_document_load_state_ = DocumentLoadState::kComplete;

  int dest_page_index = preview_pages_info_.front().dest_page_index;
  preview_pages_info_.pop();
  engine_->AppendPage(preview_engine_.get(), dest_page_index);

  ++print_preview_loaded_page_count_;
  LoadNextPreviewPage();
}

void PdfViewWebPlugin::PreviewDocumentLoadFailed() {
  client_->RecordComputedAction("PDF.PreviewDocumentLoadFailure");
  if (preview_document_load_state_ != DocumentLoadState::kLoading ||
      preview_pages_info_.empty()) {
    return;
  }

  // Even if a print preview page failed to load, keep going.
  preview_document_load_state_ = DocumentLoadState::kFailed;
  preview_pages_info_.pop();
  ++print_preview_loaded_page_count_;
  LoadNextPreviewPage();
}

void PdfViewWebPlugin::LoadNextPreviewPage() {
  if (!preview_pages_info_.empty()) {
    DCHECK_LT(print_preview_loaded_page_count_, print_preview_page_count_);
    LoadAvailablePreviewPage();
    return;
  }

  if (print_preview_loaded_page_count_ == print_preview_page_count_) {
    SendPrintPreviewLoadedNotification();
  }
}

void PdfViewWebPlugin::SendPrintPreviewLoadedNotification() {
  client_->PostMessage(base::DictValue().Set("type", "printPreviewLoaded"));
}

void PdfViewWebPlugin::CreateAgent(
    mojo::PendingRemote<blink::mojom::AnnotationAgentHost> host_remote,
    mojo::PendingReceiver<blink::mojom::AnnotationAgent> agent_receiver,
    blink::mojom::AnnotationType type,
    blink::mojom::SelectorPtr selector,
    std::optional<int> search_range_start_node_id) {
  annotation_agent_ = std::make_unique<PdfAnnotationAgent>(
      engine_.get(), type, std::move(selector), std::move(host_remote),
      std::move(agent_receiver));
}

void PdfViewWebPlugin::CreateAgentFromSelection(
    blink::mojom::AnnotationType type,
    CreateAgentFromSelectionCallback callback) {
  // Currently, there is no immediate plan to support the "Copy Link to
  // Highlight" context menu.
  NOTIMPLEMENTED();
}

void PdfViewWebPlugin::RemoveAgentsOfType(blink::mojom::AnnotationType type) {
  // TODO(crbug.com/395859365): Implement the "Remove highlight" from the
  // right-click's context menu.
}

void PdfViewWebPlugin::SendThumbnailForTesting(base::DictValue reply,
                                               int page_index,
                                               Thumbnail thumbnail) {
  SendThumbnail(std::move(reply), page_index, std::move(thumbnail));
}

void PdfViewWebPlugin::SendThumbnail(base::DictValue reply,
                                     int page_index,
                                     Thumbnail thumbnail) {
  DCHECK_EQ(*reply.FindString("type"), "getThumbnailReply");
  DCHECK(reply.FindString("messageId"));

  reply.Set("imageData", thumbnail.TakeData());
  reply.Set("width", thumbnail.image_size().width());
  reply.Set("height", thumbnail.image_size().height());
  client_->PostMessage(std::move(reply));
}

#if BUILDFLAG(ENABLE_PDF_INK2)
// static
std::unique_ptr<PdfInkModuleClient>
PdfViewWebPlugin::MaybeCreatePdfInkModuleClient(PdfViewWebPlugin& plugin) {
  if (!base::FeatureList::IsEnabled(features::kPdfInk2)) {
    return nullptr;
  }
  return std::make_unique<PdfInkModuleClientImpl>(plugin);
}

// static
std::unique_ptr<PdfInkModule> PdfViewWebPlugin::MaybeCreatePdfInkModule(
    PdfInkModuleClient* client) {
  if (!base::FeatureList::IsEnabled(features::kPdfInk2)) {
    return nullptr;
  }
  return std::make_unique<PdfInkModule>(*client);
}
#endif  // BUILDFLAG(ENABLE_PDF_INK2)

gfx::Point PdfViewWebPlugin::FrameToPdfCoordinates(
    const gfx::PointF& frame_coordinates) const {
  // TODO(crbug.com/40817151): Use methods on `blink::WebPluginContainer`.
  return gfx::ToFlooredPoint(
             gfx::ScalePoint(frame_coordinates, device_scale_)) -
         gfx::Vector2d(available_area_.x(), 0);
}

std::unique_ptr<AccessibilityDocInfo>
PdfViewWebPlugin::GetAccessibilityDocInfo() const {
  auto doc_info = std::make_unique<AccessibilityDocInfo>();
  doc_info->page_count = engine_->GetNumberOfPages();
  if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfTags)) {
    doc_info->is_tagged = engine_->IsPDFDocTagged();
    if (doc_info->is_tagged) {
      doc_info->structure_tree_root = engine_->GetStructureTree();
    }
  }
  doc_info->text_accessible =
      engine_->HasPermission(DocumentPermission::kCopyAccessible);
  doc_info->text_copyable = engine_->HasPermission(DocumentPermission::kCopy);
  return doc_info;
}

void PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo(int32_t page_index) {
  // Ignore outdated or out of range calls.
  if (page_index != next_accessibility_page_index_ || page_index < 0 ||
      page_index >= engine_->GetNumberOfPages()) {
    return;
  }

#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
  // Wait for the page to be loaded and searchified before getting accessibility
  // page info.
  // Ensure page is loaded so that it can schedule a searchify operation if
  // needed.
  engine_->GetPage(page_index)->GetPage();
  if (engine_->IsPageScheduledForSearchify(page_index)) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo,
                       weak_factory_.GetWeakPtr(), page_index),
        kAccessibilityPageDelay * 10);
    return;
  }
#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)

  ++next_accessibility_page_index_;

  AccessibilityPageInfo page_info;
  std::vector<AccessibilityTextRunInfo> text_runs;
  std::vector<AccessibilityCharInfo> chars;
  AccessibilityPageObjects page_objects;

  GetAccessibilityInfo(engine_.get(), page_index, page_info, text_runs, chars,
                       page_objects);

  pdf_accessibility_data_handler_->SetAccessibilityPageInfo(
      std::move(page_info), std::move(text_runs), std::move(chars),
      std::move(page_objects));

  // Schedule loading the next page if there's more.
  if (page_index + 1 < engine_->GetNumberOfPages()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo,
                       weak_factory_.GetWeakPtr(), page_index + 1),
        kAccessibilityPageDelay);
  }
}

void PdfViewWebPlugin::PrepareAndSetAccessibilityViewportInfo() {
  AccessibilityViewportInfo viewport_info;
  viewport_info.offset = gfx::ScaleToFlooredPoint(available_area_.origin(),
                                                  1 / (device_scale_ * zoom_));
  viewport_info.zoom = zoom_;
  viewport_info.scale = device_scale_;
  viewport_info.orientation =
      static_cast<int32_t>(engine_->GetCurrentOrientation());
  viewport_info.focus_info = {FocusObjectType::kNone, 0, 0};

  std::optional<Selection> selection = engine_->GetSelection();
  if (selection.has_value()) {
    viewport_info.selection = *selection;
  }

  pdf_accessibility_data_handler_->SetAccessibilityViewportInfo(
      std::move(viewport_info));
}

void PdfViewWebPlugin::LoadAccessibility() {
  accessibility_state_ = AccessibilityState::kLoaded;

  // A new document layout will trigger the creation of a new accessibility
  // tree, so `next_accessibility_page_index_` should be reset to ignore
  // outdated asynchronous calls of PrepareAndSetAccessibilityPageInfo().
  next_accessibility_page_index_ = 0;
  pdf_accessibility_data_handler_->SetAccessibilityDocInfo(
      GetAccessibilityDocInfo());

  // Record whether the PDF is tagged when opened by an accessibility user.
  if (metrics_handler_) {
    metrics_handler_->RecordAccessibilityIsDocTagged(engine_->IsPDFDocTagged());
  }

  // If the document contents isn't accessible, don't send anything more.
  if (!(engine_->HasPermission(DocumentPermission::kCopy) ||
        engine_->HasPermission(DocumentPermission::kCopyAccessible))) {
    return;
  }

  PrepareAndSetAccessibilityViewportInfo();

  // Schedule loading the first page.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo,
                     weak_factory_.GetWeakPtr(), /*page_index=*/0),
      kAccessibilityPageDelay);
}

void PdfViewWebPlugin::ApplyAndObserveRendererPreferences() {
  if (IsPrintPreview() || !Container()) {
    return;
  }

  blink::WebLocalFrame* frame = Container()->GetDocument().GetFrame();
  if (!frame) {
    return;
  }

  blink::WebView* view = frame->View();
  if (!view) {
    return;
  }

  frame->SetIsCaretBrowsingOverridden(true);

  OnRendererPreferencesUpdated(view->GetRendererPreferences());

  blink::WebViewObserver::Observe(view);
}

}  // namespace chrome_pdf
