diff --git a/pdf/pdf_ink_module.cc b/pdf/pdf_ink_module.cc
index ab0aa165bba4f..f6f4aaeba62e4 100644
--- a/pdf/pdf_ink_module.cc
+++ b/pdf/pdf_ink_module.cc
@@ -171,6 +171,38 @@ void PdfInkModule::Draw(SkCanvas& canvas) {
   }
 }
 
+void PdfInkModule::GenerateAndSendInkThumbnail(
+    int page_index,
+    const gfx::Size& thumbnail_size) {
+  CHECK(!thumbnail_size.IsEmpty());
+
+  auto info = SkImageInfo::Make(thumbnail_size.width(), thumbnail_size.height(),
+                                kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
+  const size_t alloc_size = info.computeMinByteSize();
+  CHECK(!SkImageInfo::ByteSizeOverflowed(alloc_size));
+  std::vector<uint8_t> image_data(alloc_size);
+
+  SkBitmap sk_bitmap;
+  sk_bitmap.installPixels(info, image_data.data(), info.minRowBytes());
+  SkCanvas canvas(sk_bitmap);
+  if (!DrawThumbnail(canvas, page_index)) {
+    return;
+  }
+
+  base::Value::Dict message;
+  message.Set("type", "updateInk2Thumbnail");
+  message.Set("pageNumber", page_index + 1);
+  message.Set("imageData", std::move(image_data));
+  message.Set("width", thumbnail_size.width());
+  message.Set("height", thumbnail_size.height());
+  client_->PostMessage(std::move(message));
+}
+
+void PdfInkModule::GenerateAndSendInkThumbnailInternal(int page_index) {
+  return GenerateAndSendInkThumbnail(page_index,
+                                     client_->GetThumbnailSize(page_index));
+}
+
 bool PdfInkModule::DrawThumbnail(SkCanvas& canvas, int page_index) {
   auto it = strokes_.find(page_index);
   if (it == strokes_.end() || it->second.empty()) {
@@ -615,7 +647,7 @@ bool PdfInkModule::FinishStroke(const gfx::PointF& position,
   }
 
   client_->StrokeFinished();
-  client_->UpdateThumbnail(state.page_index);
+  GenerateAndSendInkThumbnailInternal(state.page_index);
 
   bool undo_redo_success = undo_redo_model_.FinishDraw();
   CHECK(undo_redo_success);
@@ -709,7 +741,7 @@ bool PdfInkModule::FinishEraseStroke(const gfx::PointF& position,
   if (!state.page_indices_with_erasures.empty()) {
     client_->StrokeFinished();
     for (int page_index : state.page_indices_with_erasures) {
-      client_->UpdateThumbnail(page_index);
+      GenerateAndSendInkThumbnailInternal(page_index);
     }
 
     ReportEraseStroke(eraser_size_, tool_type);
@@ -1174,7 +1206,7 @@ void PdfInkModule::ApplyUndoRedoCommandsHelper(
   }
 
   for (int page_index : page_indices_with_thumbnail_updates) {
-    client_->UpdateThumbnail(page_index);
+    GenerateAndSendInkThumbnailInternal(page_index);
   }
 }
 
diff --git a/pdf/pdf_ink_module.h b/pdf/pdf_ink_module.h
index dbec3ee9c8bd0..ab6e476e0eec6 100644
--- a/pdf/pdf_ink_module.h
+++ b/pdf/pdf_ink_module.h
@@ -140,10 +140,12 @@ class PdfInkModule {
   // stroke state with non-empty `drawing_stroke_state().inputs`.
   void Draw(SkCanvas& canvas);
 
-  // Draws `strokes_` for `page_index` into `canvas`. Here, `canvas` only covers
-  // the region for the page at `page_index`, so this only draws strokes for
-  // that page, regardless of page visibility.
-  bool DrawThumbnail(SkCanvas& canvas, int page_index);
+  // Generates a thumbnail of `thumbnail_size` for the page at `page_index`
+  // using DrawThumbnail(). Sends the result to the WebUI if successful.
+  // Otherwise, do not send anything to the WebUI.
+  // `thumbnail_size` must be non-empty.
+  void GenerateAndSendInkThumbnail(int page_index,
+                                   const gfx::Size& thumbnail_size);
 
   // Gets an iterator for the visible strokes across all pages.
   // Modifying the set of visible strokes while using the iterator is not
@@ -392,6 +394,16 @@ class PdfInkModule {
 
   void MaybeSetDrawingBrushAndCursor();
 
+  // Helper that calls GenerateAndSendInkThumbnail() without needing to specify
+  // the thumbnail size. This helper determines the size by asking
+  // PdfInkModuleClient.
+  void GenerateAndSendInkThumbnailInternal(int page_index);
+
+  // Draws `strokes_` for `page_index` into `canvas`. Here, `canvas` only covers
+  // the region for the page at `page_index`, so this only draws strokes for
+  // that page, regardless of page visibility.
+  bool DrawThumbnail(SkCanvas& canvas, int page_index);
+
   const raw_ref<PdfInkModuleClient> client_;
 
   bool enabled_ = false;
diff --git a/pdf/pdf_ink_module_client.h b/pdf/pdf_ink_module_client.h
index de72082ff9b27..3f82dd8a6cd26 100644
--- a/pdf/pdf_ink_module_client.h
+++ b/pdf/pdf_ink_module_client.h
@@ -52,6 +52,10 @@ class PdfInkModuleClient {
   // non-negative page index returned from `VisiblePageIndexFromPoint()`.
   virtual gfx::Rect GetPageContentsRect(int page_index) = 0;
 
+  // Gets the thumbnail size for `page_index`. The size must be non-empty for
+  // any valid page index.
+  virtual gfx::Size GetThumbnailSize(int page_index) = 0;
+
   // Gets the offset within the rendering viewport to where the page images
   // will be drawn.  Since the offset is a location within the viewport, it
   // must always contain non-negative values.  Values are in scaled CSS
@@ -103,9 +107,6 @@ class PdfInkModuleClient {
   virtual void UpdateStrokeActive(int page_index, InkStrokeId id, bool active) {
   }
 
-  // Asks the client to update the thumbnail for `page_index`.
-  virtual void UpdateThumbnail(int page_index) {}
-
   // Returns the 0-based page index for the given `point` if it is on a
   // visible page, or -1 if `point` is not on a visible page.
   virtual int VisiblePageIndexFromPoint(const gfx::PointF& point) = 0;
diff --git a/pdf/pdf_ink_module_unittest.cc b/pdf/pdf_ink_module_unittest.cc
index d65f607a5afd6..dbc31bb13b602 100644
--- a/pdf/pdf_ink_module_unittest.cc
+++ b/pdf/pdf_ink_module_unittest.cc
@@ -29,6 +29,7 @@
 #include "pdf/pdfium/pdfium_ink_reader.h"
 #include "pdf/test/mouse_event_builder.h"
 #include "pdf/test/pdf_ink_test_helpers.h"
+#include "pdf/ui/thumbnail.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_mouse_event.h"
@@ -251,6 +252,14 @@ class FakeClient : public PdfInkModuleClient {
 
   PageOrientation GetOrientation() const override { return orientation_; }
 
+  gfx::Size GetThumbnailSize(int page_index) override {
+    CHECK_GE(page_index, 0);
+    CHECK_LT(static_cast<size_t>(page_index), page_layouts_.size());
+    Thumbnail thumbnail(page_layouts_[page_index].size(),
+                        /*device_pixel_ratio=*/1);
+    return thumbnail.image_size();
+  }
+
   gfx::Vector2dF GetViewportOriginOffset() override {
     return viewport_origin_offset_;
   }
@@ -297,10 +306,6 @@ class FakeClient : public PdfInkModuleClient {
               (int page_index, InkStrokeId id, bool active),
               (override));
 
-  void UpdateThumbnail(int page_index) override {
-    updated_thumbnail_page_indices_.push_back(page_index);
-  }
-
   int VisiblePageIndexFromPoint(const gfx::PointF& point) override {
     for (size_t i = 0; i < page_layouts_.size(); ++i) {
       if (IsPageVisible(i) && page_layouts_[i].Contains(point)) {
@@ -314,10 +319,6 @@ class FakeClient : public PdfInkModuleClient {
 
   int stroke_finished_count() const { return stroke_finished_count_; }
 
-  const std::vector<int>& updated_thumbnail_page_indices() const {
-    return updated_thumbnail_page_indices_;
-  }
-
   const std::vector<gfx::Rect>& invalidations() const { return invalidations_; }
 
   // Provide the sequence of pages and the coordinates and dimensions for how
@@ -349,7 +350,6 @@ class FakeClient : public PdfInkModuleClient {
 
  private:
   int stroke_finished_count_ = 0;
-  std::vector<int> updated_thumbnail_page_indices_;
   std::vector<gfx::RectF> page_layouts_;
   std::set<int> visible_page_indices_;
   PageOrientation orientation_ = PageOrientation::kOriginal;
@@ -750,6 +750,24 @@ class PdfInkModuleStrokeTest : public PdfInkModuleTest {
   static constexpr gfx::PointF kMousePoints[] = {
       kMouseDownPoint, kMouseMovePoint, kMouseUpPoint};
 
+  // PdfInkModuleTest:
+  void SetUp() override {
+    PdfInkModuleTest::SetUp();
+
+    EXPECT_CALL(client(), PostMessage)
+        .WillRepeatedly([&](const base::Value::Dict& dict) {
+          const std::string* type = dict.FindString("type");
+          ASSERT_TRUE(type);
+          if (*type != "updateInk2Thumbnail") {
+            return;
+          }
+
+          std::optional<int> page_number = dict.FindInt("pageNumber");
+          ASSERT_TRUE(page_number.has_value());
+          updated_thumbnail_page_indices_.push_back(page_number.value() - 1);
+        });
+  }
+
   void InitializeSimpleSinglePageBasicLayout() {
     // Single page layout that matches visible area.
     constexpr gfx::RectF kPage(0.0f, 0.0f, 50.0f, 60.0f);
@@ -992,6 +1010,10 @@ class PdfInkModuleStrokeTest : public PdfInkModuleTest {
     testing::Mock::VerifyAndClearExpectations(this);
   }
 
+  const std::vector<int>& updated_thumbnail_page_indices() const {
+    return updated_thumbnail_page_indices_;
+  }
+
  private:
   void ApplyStrokeWithMouseAtPointsMaybeHandled(
       const gfx::PointF& mouse_down_point,
@@ -1062,14 +1084,14 @@ class PdfInkModuleStrokeTest : public PdfInkModuleTest {
 
   void ValidateRunStrokeCheckTest(bool expect_stroke_success) {
     EXPECT_EQ(expect_stroke_success ? 1 : 0, client().stroke_finished_count());
-    const std::vector<int>& updated_thumbnail_page_indices =
-        client().updated_thumbnail_page_indices();
     if (expect_stroke_success) {
-      EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+      EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
     } else {
-      EXPECT_TRUE(updated_thumbnail_page_indices.empty());
+      EXPECT_TRUE(updated_thumbnail_page_indices().empty());
     }
   }
+
+  std::vector<int> updated_thumbnail_page_indices_;
 };
 
 TEST_F(PdfInkModuleStrokeTest, NoAnnotationWithMouseIfNotEnabled) {
@@ -1479,9 +1501,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStroke) {
       VisibleStrokeInputPositions(),
       ElementsAre(Pair(0, ElementsAre(ElementsAreArray(kMousePoints)))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // Stroke with the eraser tool.
   SelectEraserToolOfSize(3.0f);
@@ -1491,7 +1511,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStroke) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Erasing counts as another stroke action.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke again. The stroke that have already been erased should stay erased.
   ApplyStrokeWithMouseAtMouseDownPoint();
@@ -1500,7 +1520,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStroke) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Nothing got erased, so the count stays at 2.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseOnPageWithoutStrokes) {
@@ -1518,7 +1538,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseOnPageWithoutStrokes) {
   // called.
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   EXPECT_EQ(0, client().stroke_finished_count());
-  EXPECT_TRUE(client().updated_thumbnail_page_indices().empty());
+  EXPECT_TRUE(updated_thumbnail_page_indices().empty());
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseStrokeEntirelyOffPage) {
@@ -1530,9 +1550,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeEntirelyOffPage) {
       VisibleStrokeInputPositions(),
       ElementsAre(Pair(0, ElementsAre(ElementsAreArray(kMousePoints)))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // Stroke with the eraser tool outside of the page.
   SelectEraserToolOfSize(3.0f);
@@ -1546,7 +1564,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeEntirelyOffPage) {
       VisibleStrokeInputPositions(),
       ElementsAre(Pair(0, ElementsAre(ElementsAreArray(kMousePoints)))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseStrokeErasesTwoStrokes) {
@@ -1568,9 +1586,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeErasesTwoStrokes) {
       Pair(0, ElementsAre(ElementsAreArray(kMousePoints), kStroke2Matcher)));
   EXPECT_THAT(VisibleStrokeInputPositions(), kVisibleStrokesMatcher);
   EXPECT_EQ(2, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke with the eraser tool at `kMouseMovePoint`, where it should
   // intersect with both strokes, but does not because InkStrokeModeler modeled
@@ -1583,7 +1599,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeErasesTwoStrokes) {
   // the strokes.
   EXPECT_THAT(VisibleStrokeInputPositions(), kVisibleStrokesMatcher);
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke with the eraser tool again at `kMousePoints`, but now with a much
   // bigger eraser size. This will actually intersect with both strokes.
@@ -1597,7 +1613,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeErasesTwoStrokes) {
   // Check that there are now no visible strokes.
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   EXPECT_EQ(3, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0, 0));
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseStrokesAcrossTwoPages) {
@@ -1607,9 +1623,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokesAcrossTwoPages) {
   // Start out without any strokes.
   EXPECT_TRUE(StrokeInputPositions().empty());
   EXPECT_EQ(0, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_TRUE(updated_thumbnail_page_indices.empty());
+  EXPECT_TRUE(updated_thumbnail_page_indices().empty());
 
   ExpectStrokesAdded(/*strokes_affected=*/2);
   ExpectNoUpdateStrokeActive();
@@ -1621,7 +1635,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokesAcrossTwoPages) {
       kTwoPageVerticalLayoutPoint3InsidePage0);
   EXPECT_THAT(StrokeInputPositions(), ElementsAre(Pair(0, SizeIs(1))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // A stroke in the second page generates a stroke only for that page.
   ApplyStrokeWithMouseAtPoints(
@@ -1631,7 +1645,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokesAcrossTwoPages) {
   EXPECT_THAT(StrokeInputPositions(),
               ElementsAre(Pair(0, SizeIs(1)), Pair(1, SizeIs(1))));
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 1));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 1));
 
   // Erasing across the two pages should erase everything.
   SelectEraserToolOfSize(3.0f);
@@ -1645,7 +1659,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokesAcrossTwoPages) {
       kTwoPageVerticalLayoutPoint3InsidePage1);
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   EXPECT_EQ(3, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 1, 0, 1));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 1, 0, 1));
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseStrokePageExitAndReentry) {
@@ -1668,9 +1682,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokePageExitAndReentry) {
                       ElementsAreArray(
                           kTwoPageVerticalLayoutPageExitAndReentrySegment2)))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // Select the eraser tool and call ApplyStrokeWithMouseAtPoints() again with
   // the same arguments.
@@ -1691,7 +1703,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokePageExitAndReentry) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Erasing counts as another stroke action.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithTouch) {
@@ -1703,9 +1715,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithTouch) {
       VisibleStrokeInputPositions(),
       ElementsAre(Pair(0, ElementsAre(ElementsAreArray(kMousePoints)))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // Stroke with the eraser tool.
   SelectEraserToolOfSize(3.0f);
@@ -1720,7 +1730,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithTouch) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Erasing counts as another stroke action.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke again. The stroke that have already been erased should stay erased.
   ApplyStrokeWithTouchAtPoints(base::span_from_ref(kMouseDownPoint),
@@ -1731,7 +1741,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithTouch) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Nothing got erased, so the count stays at 2.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke again with the mouse gets the same results.
   ApplyStrokeWithMouseAtMouseDownPoint();
@@ -1740,7 +1750,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithTouch) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Nothing got erased, so the count stays at 2.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 }
 
 TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithPen) {
@@ -1752,9 +1762,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithPen) {
       VisibleStrokeInputPositions(),
       ElementsAre(Pair(0, ElementsAre(ElementsAreArray(kMousePoints)))));
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // Stroke with the eraser tool.
   SelectEraserToolOfSize(3.0f);
@@ -1769,7 +1777,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithPen) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Erasing counts as another stroke action.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke again. The stroke that have already been erased should stay erased.
   ApplyStrokeWithPenAtPoints(base::span_from_ref(kMouseDownPoint),
@@ -1780,7 +1788,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithPen) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Nothing got erased, so the count stays at 2.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Stroke again with the mouse gets the same results.
   ApplyStrokeWithMouseAtMouseDownPoint();
@@ -1789,7 +1797,7 @@ TEST_F(PdfInkModuleStrokeTest, EraseStrokeWithPen) {
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Nothing got erased, so the count stays at 2.
   EXPECT_EQ(2, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 }
 
 TEST_F(PdfInkModuleStrokeTest, RunStrokeMissedEndEventDuringDrawing) {
@@ -2239,16 +2247,14 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoBasic) {
   EXPECT_THAT(VisibleStrokeInputPositions(), kMatcher);
   // RunStrokeCheckTest() performed the only stroke.
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   PerformUndo();
   EXPECT_THAT(StrokeInputPositions(), kMatcher);
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   // Undo/redo here and below do not trigger StrokeFinished().
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   // Spurious undo message is a no-op.
   VerifyAndClearExpectations();
@@ -2258,7 +2264,7 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoBasic) {
   EXPECT_THAT(StrokeInputPositions(), kMatcher);
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   VerifyAndClearExpectations();
   ExpectNoStrokeAdded();
@@ -2267,7 +2273,7 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoBasic) {
   EXPECT_THAT(StrokeInputPositions(), kMatcher);
   EXPECT_THAT(VisibleStrokeInputPositions(), kMatcher);
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0, 0));
 
   // Spurious redo message is a no-op.
   VerifyAndClearExpectations();
@@ -2277,7 +2283,7 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoBasic) {
   EXPECT_THAT(StrokeInputPositions(), kMatcher);
   EXPECT_THAT(VisibleStrokeInputPositions(), kMatcher);
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0, 0));
 }
 
 TEST_F(PdfInkModuleUndoRedoTest, UndoRedoInvalidationsBasic) {
@@ -2368,9 +2374,7 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoAnnotationModeDisabled) {
   EXPECT_THAT(VisibleStrokeInputPositions(), kMatcher);
   // RunStrokeCheckTest() performed the only stroke.
   EXPECT_EQ(1, client().stroke_finished_count());
-  const std::vector<int>& updated_thumbnail_page_indices =
-      client().updated_thumbnail_page_indices();
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0));
 
   // Disable annotation mode. Undo/redo should still work.
   EXPECT_TRUE(
@@ -2381,13 +2385,13 @@ TEST_F(PdfInkModuleUndoRedoTest, UndoRedoAnnotationModeDisabled) {
   EXPECT_THAT(StrokeInputPositions(), kMatcher);
   EXPECT_TRUE(VisibleStrokeInputPositions().empty());
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0));
 
   PerformRedo();
   EXPECT_THAT(StrokeInputPositions(), kMatcher);
   EXPECT_THAT(VisibleStrokeInputPositions(), kMatcher);
   EXPECT_EQ(1, client().stroke_finished_count());
-  EXPECT_THAT(updated_thumbnail_page_indices, ElementsAre(0, 0, 0));
+  EXPECT_THAT(updated_thumbnail_page_indices(), ElementsAre(0, 0, 0));
 }
 
 TEST_F(PdfInkModuleUndoRedoTest, UndoRedoBetweenDraws) {
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 57323cfad1cc8..e99c99a9190ec 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -291,6 +291,11 @@ class PdfViewWebPlugin::PdfInkModuleClientImpl : public PdfInkModuleClient {
     return plugin_->engine_->GetPageContentsRect(page_index);
   }
 
+  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();
   }
@@ -365,12 +370,6 @@ class PdfViewWebPlugin::PdfInkModuleClientImpl : public PdfInkModuleClient {
     plugin_->engine_->UpdateStrokeActive(page_index, id, active);
   }
 
-  void UpdateThumbnail(int page_index) override {
-    plugin_->GenerateAndSendInkThumbnail(
-        page_index,
-        plugin_->engine_->GetThumbnailSize(page_index, plugin_->device_scale_));
-  }
-
   int VisiblePageIndexFromPoint(const gfx::PointF& point) override {
     for (int i = 0; i < plugin_->engine_->GetNumberOfPages(); ++i) {
       if (!IsPageVisible(i)) {
@@ -2754,7 +2753,8 @@ void PdfViewWebPlugin::SendThumbnail(base::Value::Dict reply,
 
 #if BUILDFLAG(ENABLE_PDF_INK2)
   if (ink_module_) {
-    GenerateAndSendInkThumbnail(page_index, thumbnail.image_size());
+    ink_module_->GenerateAndSendInkThumbnail(page_index,
+                                             thumbnail.image_size());
   }
 #endif
 }
@@ -2777,33 +2777,6 @@ std::unique_ptr<PdfInkModule> PdfViewWebPlugin::MaybeCreatePdfInkModule(
   }
   return std::make_unique<PdfInkModule>(*client);
 }
-
-void PdfViewWebPlugin::GenerateAndSendInkThumbnail(int page_index,
-                                                   const gfx::Size& size) {
-  CHECK(!size.IsEmpty());
-  CHECK(ink_module_);
-
-  auto info = SkImageInfo::Make(size.width(), size.height(),
-                                kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
-  const size_t alloc_size = info.computeMinByteSize();
-  CHECK(!SkImageInfo::ByteSizeOverflowed(alloc_size));
-  std::vector<uint8_t> image_data(alloc_size);
-
-  SkBitmap sk_bitmap;
-  sk_bitmap.installPixels(info, image_data.data(), info.minRowBytes());
-  SkCanvas canvas(sk_bitmap);
-  if (!ink_module_->DrawThumbnail(canvas, page_index)) {
-    return;
-  }
-
-  base::Value::Dict message;
-  message.Set("type", "updateInk2Thumbnail");
-  message.Set("pageNumber", page_index + 1);
-  message.Set("imageData", std::move(image_data));
-  message.Set("width", size.width());
-  message.Set("height", size.height());
-  client_->PostMessage(std::move(message));
-}
 #endif  // BUILDFLAG(ENABLE_PDF_INK2)
 
 gfx::Point PdfViewWebPlugin::FrameToPdfCoordinates(
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 886d8d06740a8..2f4912ede5c08 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -688,8 +688,6 @@ class PdfViewWebPlugin final : public PDFiumEngineClient,
       PdfViewWebPlugin& plugin);
   static std::unique_ptr<PdfInkModule> MaybeCreatePdfInkModule(
       PdfInkModuleClient* client);
-
-  void GenerateAndSendInkThumbnail(int page_index, const gfx::Size& size);
 #endif
 
   // Converts `frame_coordinates` to PDF coordinates.
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
index 50939b98987ad..54ec7a38b413a 100644
--- a/pdf/pdf_view_web_plugin_unittest.cc
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -2766,6 +2766,13 @@ TEST_F(PdfViewWebPluginInkTest, UpdateCursor) {
   EXPECT_EQ(ui::mojom::CursorType::kPointer, cursor.type());
 }
 
+TEST_F(PdfViewWebPluginInkTest, GetThumbnailSize) {
+  SetUpWithTrivialInkStrokes();
+  EXPECT_EQ(gfx::Size(50, 25),
+            plugin_->ink_module_client_for_testing()->GetThumbnailSize(
+                /*page_index=*/0));
+}
+
 TEST_F(PdfViewWebPluginInkTest, GetZoom) {
   // Demonstrate that default zoom is identity.
   EXPECT_EQ(1.0f, plugin_->ink_module_client_for_testing()->GetZoom());
@@ -2796,36 +2803,6 @@ TEST_F(PdfViewWebPluginInkTest, GetZoom) {
   EXPECT_EQ(2.5f, plugin_->ink_module_client_for_testing()->GetZoom());
 }
 
-TEST_F(PdfViewWebPluginInkTest, UpdateThumbnail) {
-  SetUpWithTrivialInkStrokes();
-
-  EXPECT_CALL(*client_ptr_, PostMessage)
-      .WillOnce([](const base::Value::Dict& dict) {
-        auto expected = base::test::ParseJsonDict(R"({
-            "type": "updateInk2Thumbnail",
-            "pageNumber": 1,
-            "width": 50,
-            "height": 25,
-        })");
-        EXPECT_THAT(dict, base::test::DictionaryHasValues(expected));
-
-        // Test `dict` contains the image data, but not the exact value.
-        const auto* blob = dict.FindBlob("imageData");
-        ASSERT_TRUE(blob);
-        EXPECT_FALSE(blob->empty());
-      });
-
-  plugin_->ink_module_client_for_testing()->UpdateThumbnail(/*page_index=*/0);
-}
-
-TEST_F(PdfViewWebPluginInkTest, UpdateThumbnailWithNoStrokes) {
-  ON_CALL(*engine_ptr_, GetThumbnailSize)
-      .WillByDefault(Return(gfx::Size(50, 25)));
-
-  EXPECT_CALL(*client_ptr_, PostMessage).Times(0);
-  plugin_->ink_module_client_for_testing()->UpdateThumbnail(/*page_index=*/0);
-}
-
 TEST_F(PdfViewWebPluginInkTest, AddUpdateDiscardStroke) {
   const PdfInkBrush kBrush(PdfInkBrush::Type::kPen, SK_ColorRED, /*size=*/4.0f);
   constexpr InkStrokeId kStrokeId(1);