[M108 Migration][HBBTV] Implement ewk_context_register_jsplugin_mime_types API
[platform/framework/web/chromium-efl.git] / pdf / pdf_view_web_plugin.cc
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "pdf/pdf_view_web_plugin.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <algorithm>
11 #include <memory>
12 #include <string>
13 #include <utility>
14 #include <vector>
15
16 #include "base/auto_reset.h"
17 #include "base/bind.h"
18 #include "base/callback.h"
19 #include "base/check_op.h"
20 #include "base/containers/fixed_flat_map.h"
21 #include "base/containers/queue.h"
22 #include "base/debug/crash_logging.h"
23 #include "base/i18n/char_iterator.h"
24 #include "base/i18n/rtl.h"
25 #include "base/i18n/string_search.h"
26 #include "base/i18n/time_formatting.h"
27 #include "base/logging.h"
28 #include "base/memory/raw_ptr.h"
29 #include "base/no_destructor.h"
30 #include "base/notreached.h"
31 #include "base/numerics/safe_conversions.h"
32 #include "base/strings/escape.h"
33 #include "base/strings/strcat.h"
34 #include "base/strings/string_number_conversions.h"
35 #include "base/strings/string_piece.h"
36 #include "base/strings/string_split.h"
37 #include "base/strings/string_util.h"
38 #include "base/thread_annotations.h"
39 #include "base/threading/sequenced_task_runner_handle.h"
40 #include "base/threading/thread_checker.h"
41 #include "base/threading/thread_task_runner_handle.h"
42 #include "base/time/time.h"
43 #include "base/values.h"
44 #include "cc/paint/paint_canvas.h"
45 #include "cc/paint/paint_flags.h"
46 #include "cc/paint/paint_image.h"
47 #include "cc/paint/paint_image_builder.h"
48 #include "net/cookies/site_for_cookies.h"
49 #include "pdf/accessibility_structs.h"
50 #include "pdf/buildflags.h"
51 #include "pdf/content_restriction.h"
52 #include "pdf/document_layout.h"
53 #include "pdf/loader/result_codes.h"
54 #include "pdf/loader/url_loader.h"
55 #include "pdf/metrics_handler.h"
56 #include "pdf/mojom/pdf.mojom.h"
57 #include "pdf/paint_ready_rect.h"
58 #include "pdf/parsed_params.h"
59 #include "pdf/pdf_accessibility_data_handler.h"
60 #include "pdf/pdf_engine.h"
61 #include "pdf/pdf_init.h"
62 #include "pdf/pdfium/pdfium_engine.h"
63 #include "pdf/post_message_receiver.h"
64 #include "pdf/ui/document_properties.h"
65 #include "pdf/ui/file_name.h"
66 #include "pdf/ui/thumbnail.h"
67 #include "printing/metafile_skia.h"
68 #include "services/network/public/mojom/referrer_policy.mojom-shared.h"
69 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
70 #include "third_party/blink/public/common/input/web_coalesced_input_event.h"
71 #include "third_party/blink/public/common/input/web_input_event.h"
72 #include "third_party/blink/public/common/input/web_keyboard_event.h"
73 #include "third_party/blink/public/common/metrics/document_update_reason.h"
74 #include "third_party/blink/public/mojom/input/focus_type.mojom-shared.h"
75 #include "third_party/blink/public/platform/platform.h"
76 #include "third_party/blink/public/platform/web_input_event_result.h"
77 #include "third_party/blink/public/platform/web_string.h"
78 #include "third_party/blink/public/platform/web_text_input_type.h"
79 #include "third_party/blink/public/platform/web_url.h"
80 #include "third_party/blink/public/platform/web_url_error.h"
81 #include "third_party/blink/public/platform/web_url_response.h"
82 #include "third_party/blink/public/web/web_associated_url_loader.h"
83 #include "third_party/blink/public/web/web_associated_url_loader_options.h"
84 #include "third_party/blink/public/web/web_document.h"
85 #include "third_party/blink/public/web/web_frame.h"
86 #include "third_party/blink/public/web/web_plugin_container.h"
87 #include "third_party/blink/public/web/web_plugin_params.h"
88 #include "third_party/blink/public/web/web_print_params.h"
89 #include "third_party/blink/public/web/web_print_preset_options.h"
90 #include "third_party/blink/public/web/web_view.h"
91 #include "third_party/blink/public/web/web_widget.h"
92 #include "third_party/skia/include/core/SkBitmap.h"
93 #include "third_party/skia/include/core/SkColor.h"
94 #include "third_party/skia/include/core/SkImage.h"
95 #include "third_party/skia/include/core/SkImageInfo.h"
96 #include "third_party/skia/include/core/SkRect.h"
97 #include "third_party/skia/include/core/SkRefCnt.h"
98 #include "third_party/skia/include/core/SkSize.h"
99 #include "ui/base/cursor/cursor.h"
100 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
101 #include "ui/base/text/bytes_formatting.h"
102 #include "ui/events/base_event_utils.h"
103 #include "ui/events/blink/blink_event_util.h"
104 #include "ui/events/keycodes/keyboard_codes.h"
105 #include "ui/gfx/geometry/point.h"
106 #include "ui/gfx/geometry/point_conversions.h"
107 #include "ui/gfx/geometry/point_f.h"
108 #include "ui/gfx/geometry/rect.h"
109 #include "ui/gfx/geometry/size.h"
110 #include "ui/gfx/geometry/skia_conversions.h"
111 #include "ui/gfx/geometry/vector2d.h"
112 #include "ui/gfx/geometry/vector2d_f.h"
113 #include "ui/gfx/range/range.h"
114 #include "url/gurl.h"
115 #include "v8/include/v8.h"
116
117 namespace chrome_pdf {
118
119 namespace {
120
121 // The minimum zoom level allowed.
122 constexpr double kMinZoom = 0.01;
123
124 constexpr base::TimeDelta kFindResultCooldown = base::Milliseconds(100);
125
126 constexpr base::StringPiece kChromeExtensionHost =
127     "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/";
128
129 // Print Preview base URL.
130 constexpr base::StringPiece kChromePrintHost = "chrome://print/";
131
132 // Untrusted Print Preview base URL.
133 constexpr base::StringPiece kChromeUntrustedPrintHost =
134     "chrome-untrusted://print/";
135
136 // Same value as `printing::COMPLETE_PREVIEW_DOCUMENT_INDEX`.
137 constexpr int kCompletePDFIndex = -1;
138
139 // A different negative value to differentiate itself from `kCompletePDFIndex`.
140 constexpr int kInvalidPDFIndex = -2;
141
142 // Enumeration of pinch states.
143 // This should match PinchPhase enum in
144 // chrome/browser/resources/pdf/viewport.ts.
145 enum class PinchPhase {
146   kNone = 0,
147   kStart = 1,
148   kUpdateZoomOut = 2,
149   kUpdateZoomIn = 3,
150   kEnd = 4,
151 };
152
153 // Initialization performed per renderer process. Initialization may be
154 // triggered from multiple plugin instances, but should only execute once.
155 //
156 // TODO(crbug.com/1123621): We may be able to simplify this once we've figured
157 // out exactly which processes need to initialize and shutdown PDFium.
158 class PerProcessInitializer final {
159  public:
160   ~PerProcessInitializer() {
161     // On some configs, thread checker is trivially destructible, which makes
162     // `PerProcessInitializer` trivially destructible as well. This is a problem
163     // because `base::NoDestructor` only allows non-trivially destructible
164     // types. Force `PerProcessInitializer` to be non-trivially destructible by
165     // declaring a non-default destructor.
166   }
167
168   static PerProcessInitializer& GetInstance() {
169     static base::NoDestructor<PerProcessInitializer> instance;
170     return *instance;
171   }
172
173   void Acquire() {
174     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
175
176     DCHECK_GE(init_count_, 0);
177     if (init_count_++ > 0)
178       return;
179
180     DCHECK(!IsSDKInitializedViaPlugin());
181     InitializeSDK(/*enable_v8=*/true, FontMappingMode::kBlink);
182     SetIsSDKInitializedViaPlugin(true);
183   }
184
185   void Release() {
186     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
187
188     DCHECK_GT(init_count_, 0);
189     if (--init_count_ > 0)
190       return;
191
192     DCHECK(IsSDKInitializedViaPlugin());
193     ShutdownSDK();
194     SetIsSDKInitializedViaPlugin(false);
195   }
196
197  private:
198   int init_count_ GUARDED_BY_CONTEXT(thread_checker_) = 0;
199
200   // TODO(crbug.com/1123731): Assuming PDFium is thread-hostile for now, and
201   // must use one thread exclusively.
202   THREAD_CHECKER(thread_checker_);
203 };
204
205 base::Value::Dict DictFromRect(const gfx::Rect& rect) {
206   base::Value::Dict dict;
207   dict.Set("x", rect.x());
208   dict.Set("y", rect.y());
209   dict.Set("width", rect.width());
210   dict.Set("height", rect.height());
211   return dict;
212 }
213
214 bool IsPrintPreviewUrl(base::StringPiece url) {
215   return base::StartsWith(url, kChromeUntrustedPrintHost);
216 }
217
218 int ExtractPrintPreviewPageIndex(base::StringPiece src_url) {
219   // Sample `src_url` format: chrome-untrusted://print/id/page_index/print.pdf
220   // The page_index is zero-based, but can be negative with special meanings.
221   std::vector<base::StringPiece> url_substr =
222       base::SplitStringPiece(src_url.substr(kChromeUntrustedPrintHost.size()),
223                              "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
224   if (url_substr.size() != 3)
225     return kInvalidPDFIndex;
226
227   if (url_substr[2] != "print.pdf")
228     return kInvalidPDFIndex;
229
230   int page_index = 0;
231   if (!base::StringToInt(url_substr[1], &page_index))
232     return kInvalidPDFIndex;
233   return page_index;
234 }
235
236 bool IsPreviewingPDF(int print_preview_page_count) {
237   return print_preview_page_count == 0;
238 }
239
240 // Prepares messages from the plugin that reply to messages from the embedder.
241 // If the "type" value of `message` is "foo", then the `reply_type` must be
242 // "fooReply". The `message` from the embedder must have a "messageId" value
243 // that will be copied to the reply message.
244 base::Value::Dict PrepareReplyMessage(base::StringPiece reply_type,
245                                       const base::Value::Dict& message) {
246   DCHECK_EQ(reply_type, *message.FindString("type") + "Reply");
247
248   base::Value::Dict reply;
249   reply.Set("type", reply_type);
250   reply.Set("messageId", *message.FindString("messageId"));
251   return reply;
252 }
253
254 bool IsSaveDataSizeValid(size_t size) {
255   return size > 0 && size <= PdfViewWebPlugin::kMaximumSavedFileSize;
256 }
257
258 }  // namespace
259
260 std::unique_ptr<PDFiumEngine> PdfViewWebPlugin::Client::CreateEngine(
261     PDFEngine::Client* client,
262     PDFiumFormFiller::ScriptOption script_option) {
263   return std::make_unique<PDFiumEngine>(client, script_option);
264 }
265
266 std::unique_ptr<PdfAccessibilityDataHandler>
267 PdfViewWebPlugin::Client::CreateAccessibilityDataHandler(
268     PdfAccessibilityActionHandler* action_handler) {
269   return nullptr;
270 }
271
272 PdfViewWebPlugin::PdfViewWebPlugin(
273     std::unique_ptr<Client> client,
274     mojo::AssociatedRemote<pdf::mojom::PdfService> pdf_service,
275     const blink::WebPluginParams& params)
276     : client_(std::move(client)),
277       pdf_service_(std::move(pdf_service)),
278       initial_params_(params),
279       pdf_accessibility_data_handler_(
280           client_->CreateAccessibilityDataHandler(this)) {
281   DCHECK(pdf_service_);
282   pdf_service_->SetListener(listener_receiver_.BindNewPipeAndPassRemote());
283 }
284
285 PdfViewWebPlugin::~PdfViewWebPlugin() = default;
286
287 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
288 const PDFiumEngine* PdfViewWebPlugin::engine() const {
289   return engine_.get();
290 }
291
292 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
293 PDFiumEngine* PdfViewWebPlugin::engine() {
294   return engine_.get();
295 }
296
297 bool PdfViewWebPlugin::Initialize(blink::WebPluginContainer* container) {
298   DCHECK(container);
299   client_->SetPluginContainer(container);
300
301   DCHECK_EQ(container->Plugin(), this);
302   return InitializeCommon();
303 }
304
305 bool PdfViewWebPlugin::InitializeForTesting() {
306   return InitializeCommon();
307 }
308
309 bool PdfViewWebPlugin::InitializeCommon() {
310   // Allow the plugin to handle touch events.
311   client_->RequestTouchEventType(
312       blink::WebPluginContainer::kTouchEventRequestTypeRaw);
313
314   // Allow the plugin to handle find requests.
315   client_->UsePluginAsFindHandler();
316
317   absl::optional<ParsedParams> params = ParseWebPluginParams(initial_params_);
318
319   // The contents of `initial_params_` are no longer needed.
320   initial_params_ = {};
321
322   if (!params.has_value())
323     return false;
324
325   // Sets crash keys like `ppapi::proxy::PDFResource::SetCrashData()`. Note that
326   // we don't set the active URL from the top-level URL, as unlike within a
327   // plugin process, the active URL changes frequently within a renderer process
328   // (see crbug.com/1266050 for details).
329   //
330   // TODO(crbug.com/1266087): If multiple PDF plugin instances share the same
331   // renderer process, the crash key will be overwritten by the newest value.
332   static base::debug::CrashKeyString* subresource_url =
333       base::debug::AllocateCrashKeyString("subresource_url",
334                                           base::debug::CrashKeySize::Size256);
335   base::debug::SetCrashKeyString(subresource_url, params->original_url);
336
337   PerProcessInitializer::GetInstance().Acquire();
338
339   // Check if the PDF is being loaded in the PDF chrome extension. We only allow
340   // the plugin to be loaded in the extension and print preview to avoid
341   // exposing sensitive APIs directly to external websites.
342   //
343   // This is enforced before creating the plugin (see
344   // `pdf::CreateInternalPlugin()`), so we just `CHECK` for defense-in-depth.
345   const std::string& embedder_origin = client_->GetEmbedderOriginString();
346   is_print_preview_ = (embedder_origin == kChromePrintHost);
347   CHECK(IsPrintPreview() || embedder_origin == kChromeExtensionHost);
348
349   full_frame_ = params->full_frame;
350   background_color_ = params->background_color;
351
352   engine_ = client_->CreateEngine(this, params->script_option);
353   DCHECK(engine_);
354
355   SendSetSmoothScrolling();
356
357   // Skip the remaining initialization when in Print Preview mode. Loading will
358   // continue after the plugin receives a "resetPrintPreviewMode" message.
359   if (IsPrintPreview())
360     return true;
361
362   last_progress_sent_ = 0;
363   LoadUrl(params->src_url, base::BindOnce(&PdfViewWebPlugin::DidOpen,
364                                           weak_factory_.GetWeakPtr()));
365   url_ = params->original_url;
366
367   // Not all edits go through the PDF plugin's form filler. The plugin instance
368   // can be restarted by exiting annotation mode on ChromeOS, which can set the
369   // document to an edited state.
370   edit_mode_ = params->has_edits;
371 #if !BUILDFLAG(ENABLE_INK)
372   DCHECK(!edit_mode_);
373 #endif  // !BUILDFLAG(ENABLE_INK)
374
375   metrics_handler_ = std::make_unique<MetricsHandler>();
376   return true;
377 }
378
379 void PdfViewWebPlugin::SendSetSmoothScrolling() {
380   base::Value::Dict message;
381   message.Set("type", "setSmoothScrolling");
382   message.Set("smoothScrolling",
383               blink::Platform::Current()->IsScrollAnimatorEnabled());
384   client_->PostMessage(std::move(message));
385 }
386
387 void PdfViewWebPlugin::DidOpen(std::unique_ptr<UrlLoader> loader,
388                                int32_t result) {
389   if (result == kSuccess) {
390     if (!engine_->HandleDocumentLoad(std::move(loader), url_)) {
391       document_load_state_ = DocumentLoadState::kLoading;
392       DocumentLoadFailed();
393     }
394   } else if (result != kErrorAborted) {
395     DocumentLoadFailed();
396   }
397 }
398
399 void PdfViewWebPlugin::Destroy() {
400   if (client_->PluginContainer()) {
401     // Explicitly destroy the PDFEngine during destruction as it may call back
402     // into this object.
403     preview_engine_.reset();
404     engine_.reset();
405     PerProcessInitializer::GetInstance().Release();
406     client_->SetPluginContainer(nullptr);
407   }
408
409   delete this;
410 }
411
412 blink::WebPluginContainer* PdfViewWebPlugin::Container() const {
413   return client_->PluginContainer();
414 }
415
416 v8::Local<v8::Object> PdfViewWebPlugin::V8ScriptableObject(
417     v8::Isolate* isolate) {
418   if (scriptable_receiver_.IsEmpty()) {
419     // TODO(crbug.com/1123731): Messages should not be handled on the renderer
420     // main thread.
421     scriptable_receiver_.Reset(
422         isolate, PostMessageReceiver::Create(
423                      isolate, client_->GetWeakPtr(), weak_factory_.GetWeakPtr(),
424                      base::SequencedTaskRunnerHandle::Get()));
425   }
426
427   return scriptable_receiver_.Get(isolate);
428 }
429
430 bool PdfViewWebPlugin::SupportsKeyboardFocus() const {
431   return !IsPrintPreview();
432 }
433
434 void PdfViewWebPlugin::UpdateAllLifecyclePhases(
435     blink::DocumentUpdateReason reason) {}
436
437 void PdfViewWebPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) {
438   // Clip the intersection of the paint rect and the plugin rect, so that
439   // painting outside the plugin or the paint rect area can be avoided.
440   // Note: `rect` is in CSS pixels. We need to use `css_plugin_rect_`
441   // to calculate the intersection.
442   SkRect invalidate_rect =
443       gfx::RectToSkRect(gfx::IntersectRects(css_plugin_rect_, rect));
444   cc::PaintCanvasAutoRestore auto_restore(canvas, /*save=*/true);
445   canvas->clipRect(invalidate_rect);
446
447   // Paint with the plugin's background color if the snapshot is not ready.
448   if (snapshot_.GetSkImageInfo().isEmpty()) {
449     cc::PaintFlags flags;
450     flags.setBlendMode(SkBlendMode::kSrc);
451     flags.setColor(GetBackgroundColor());
452     canvas->drawRect(invalidate_rect, flags);
453     return;
454   }
455
456   // Layer translate is independent of scaling, so apply first.
457   if (!total_translate_.IsZero())
458     canvas->translate(total_translate_.x(), total_translate_.y());
459
460   if (device_to_css_scale_ != 1.0f)
461     canvas->scale(device_to_css_scale_, device_to_css_scale_);
462
463   // Position layer at plugin origin before layer scaling.
464   if (!plugin_rect_.origin().IsOrigin())
465     canvas->translate(plugin_rect_.x(), plugin_rect_.y());
466
467   if (snapshot_scale_ != 1.0f)
468     canvas->scale(snapshot_scale_, snapshot_scale_);
469
470   canvas->drawImage(snapshot_, 0, 0);
471 }
472
473 void PdfViewWebPlugin::UpdateGeometry(const gfx::Rect& window_rect,
474                                       const gfx::Rect& clip_rect,
475                                       const gfx::Rect& unobscured_rect,
476                                       bool is_visible) {
477   // An empty `window_rect` can be received here in the following cases:
478   // - If the embedded plugin size is 0.
479   // - If the embedded plugin size is not 0, it can come from re-layouts during
480   //   the plugin initialization.
481   // For either case, there is no need to create a graphic device to display
482   // a PDF in an empty window. Since an empty `window_rect` can cause failure
483   // to create the graphic device, avoid all updates on the geometries and the
484   // device scales used by the plugin, the PaintManager and the PDFiumEngine
485   // unless a non-empty `window_rect` is received.
486   if (window_rect.IsEmpty())
487     return;
488
489   OnViewportChanged(window_rect, client_->DeviceScaleFactor());
490
491   gfx::PointF scroll_position = client_->GetScrollPosition();
492   // Convert back to CSS pixels.
493   scroll_position.Scale(1.0f / device_scale_);
494   UpdateScroll(scroll_position);
495 }
496
497 void PdfViewWebPlugin::UpdateScroll(const gfx::PointF& scroll_position) {
498   if (stop_scrolling_)
499     return;
500
501   float max_x = std::max(document_size_.width() * static_cast<float>(zoom_) -
502                              plugin_dip_size_.width(),
503                          0.0f);
504   float max_y = std::max(document_size_.height() * static_cast<float>(zoom_) -
505                              plugin_dip_size_.height(),
506                          0.0f);
507
508   gfx::PointF scaled_scroll_position(
509       base::clamp(scroll_position.x(), 0.0f, max_x),
510       base::clamp(scroll_position.y(), 0.0f, max_y));
511   scaled_scroll_position.Scale(device_scale_);
512
513   engine_->ScrolledToXPosition(scaled_scroll_position.x());
514   engine_->ScrolledToYPosition(scaled_scroll_position.y());
515 }
516
517 void PdfViewWebPlugin::UpdateFocus(bool focused,
518                                    blink::mojom::FocusType focus_type) {
519   if (has_focus_ != focused) {
520     engine_->UpdateFocus(focused);
521     client_->UpdateTextInputState();
522     client_->UpdateSelectionBounds();
523   }
524   has_focus_ = focused;
525
526   if (!has_focus_ || !SupportsKeyboardFocus())
527     return;
528
529   if (focus_type != blink::mojom::FocusType::kBackward &&
530       focus_type != blink::mojom::FocusType::kForward) {
531     return;
532   }
533
534   const int modifiers = focus_type == blink::mojom::FocusType::kForward
535                             ? blink::WebInputEvent::kNoModifiers
536                             : blink::WebInputEvent::kShiftKey;
537
538   blink::WebKeyboardEvent simulated_event(blink::WebInputEvent::Type::kKeyDown,
539                                           modifiers, base::TimeTicks());
540   simulated_event.windows_key_code = ui::KeyboardCode::VKEY_TAB;
541   HandleWebInputEvent(simulated_event);
542 }
543
544 void PdfViewWebPlugin::UpdateVisibility(bool visibility) {}
545
546 blink::WebInputEventResult PdfViewWebPlugin::HandleInputEvent(
547     const blink::WebCoalescedInputEvent& event,
548     ui::Cursor* cursor) {
549   // TODO(crbug.com/1302059): The input events received by the Pepper plugin
550   // already have the viewport-to-DIP scale applied. The scaling done here
551   // should be moved into `HandleWebInputEvent()` once the Pepper plugin is
552   // removed.
553   std::unique_ptr<blink::WebInputEvent> scaled_event =
554       ui::ScaleWebInputEvent(event.Event(), viewport_to_dip_scale_);
555
556   const blink::WebInputEvent& event_to_handle =
557       scaled_event ? *scaled_event : event.Event();
558
559   const blink::WebInputEventResult result =
560       HandleWebInputEvent(event_to_handle)
561           ? blink::WebInputEventResult::kHandledApplication
562           : blink::WebInputEventResult::kNotHandled;
563
564   *cursor = cursor_type_;
565
566   return result;
567 }
568
569 void PdfViewWebPlugin::DidReceiveResponse(
570     const blink::WebURLResponse& response) {}
571
572 void PdfViewWebPlugin::DidReceiveData(const char* data, size_t data_length) {}
573
574 void PdfViewWebPlugin::DidFinishLoading() {}
575
576 void PdfViewWebPlugin::DidFailLoading(const blink::WebURLError& error) {}
577
578 bool PdfViewWebPlugin::SupportsPaginatedPrint() {
579   return true;
580 }
581
582 bool PdfViewWebPlugin::GetPrintPresetOptionsFromDocument(
583     blink::WebPrintPresetOptions* print_preset_options) {
584   print_preset_options->is_scaling_disabled = !engine_->GetPrintScaling();
585   print_preset_options->copies = engine_->GetCopiesToPrint();
586   print_preset_options->duplex_mode = engine_->GetDuplexMode();
587   print_preset_options->uniform_page_size = engine_->GetUniformPageSizePoints();
588   return true;
589 }
590
591 int PdfViewWebPlugin::PrintBegin(const blink::WebPrintParams& print_params) {
592   // The returned value is always equal to the number of pages in the PDF
593   // document irrespective of the printable area.
594   int32_t ret = engine_->GetNumberOfPages();
595   if (!ret)
596     return 0;
597
598   if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality))
599     return 0;
600
601   print_params_ = print_params;
602   if (!engine_->HasPermission(DocumentPermission::kPrintHighQuality))
603     print_params_->rasterize_pdf = true;
604
605   engine_->PrintBegin();
606   return ret;
607 }
608
609 void PdfViewWebPlugin::PrintPage(int page_number, cc::PaintCanvas* canvas) {
610   // The entire document goes into one metafile. However, it is impossible to
611   // know if a call to `PrintPage()` is the last call. Thus, `PrintPage()` just
612   // stores the pages to print and the metafile. Eventually, the printed output
613   // is generated in `PrintEnd()` and copied over to the metafile.
614
615   // Every `canvas` passed to this method should have a valid `metafile`.
616   printing::MetafileSkia* metafile = canvas->GetPrintingMetafile();
617   DCHECK(metafile);
618
619   // `pages_to_print_` should be empty iff `printing_metafile_` is not set.
620   DCHECK_EQ(pages_to_print_.empty(), !printing_metafile_);
621
622   // The metafile should be the same across all calls for a given print job.
623   DCHECK(!printing_metafile_ || (printing_metafile_ == metafile));
624
625   if (!printing_metafile_)
626     printing_metafile_ = metafile;
627
628   pages_to_print_.push_back(page_number);
629 }
630
631 void PdfViewWebPlugin::PrintEnd() {
632   if (pages_to_print_.empty())
633     return;
634
635   print_pages_called_ = true;
636   printing_metafile_->InitFromData(
637       engine_->PrintPages(pages_to_print_, print_params_.value()));
638
639   if (print_pages_called_)
640     client_->RecordComputedAction("PDF.PrintPage");
641   print_pages_called_ = false;
642   print_params_.reset();
643   engine_->PrintEnd();
644
645   printing_metafile_ = nullptr;
646   pages_to_print_.clear();
647 }
648
649 bool PdfViewWebPlugin::HasSelection() const {
650   return !selected_text_.IsEmpty();
651 }
652
653 blink::WebString PdfViewWebPlugin::SelectionAsText() const {
654   return selected_text_;
655 }
656
657 blink::WebString PdfViewWebPlugin::SelectionAsMarkup() const {
658   return selected_text_;
659 }
660
661 bool PdfViewWebPlugin::CanEditText() const {
662   return engine_->CanEditText();
663 }
664
665 bool PdfViewWebPlugin::HasEditableText() const {
666   return engine_->HasEditableText();
667 }
668
669 bool PdfViewWebPlugin::CanUndo() const {
670   return engine_->CanUndo();
671 }
672
673 bool PdfViewWebPlugin::CanRedo() const {
674   return engine_->CanRedo();
675 }
676
677 bool PdfViewWebPlugin::CanCopy() const {
678   return engine_->HasPermission(DocumentPermission::kCopy);
679 }
680
681 bool PdfViewWebPlugin::ExecuteEditCommand(const blink::WebString& name,
682                                           const blink::WebString& value) {
683   if (name == "SelectAll")
684     return SelectAll();
685
686   if (name == "Cut")
687     return Cut();
688
689   if (name == "Paste" || name == "PasteAndMatchStyle")
690     return Paste(value);
691
692   if (name == "Undo")
693     return Undo();
694
695   if (name == "Redo")
696     return Redo();
697
698   return false;
699 }
700
701 blink::WebURL PdfViewWebPlugin::LinkAtPosition(
702     const gfx::Point& /*position*/) const {
703   return GURL(link_under_cursor_);
704 }
705
706 bool PdfViewWebPlugin::StartFind(const blink::WebString& search_text,
707                                  bool case_sensitive,
708                                  int identifier) {
709   ResetRecentlySentFindUpdate();
710   find_identifier_ = identifier;
711   engine_->StartFind(search_text.Utf8(), case_sensitive);
712   return true;
713 }
714
715 void PdfViewWebPlugin::SelectFindResult(bool forward, int identifier) {
716   find_identifier_ = identifier;
717   engine_->SelectFindResult(forward);
718 }
719
720 void PdfViewWebPlugin::StopFind() {
721   find_identifier_ = -1;
722   engine_->StopFind();
723   tickmarks_.clear();
724   client_->ReportFindInPageTickmarks(tickmarks_);
725 }
726
727 bool PdfViewWebPlugin::CanRotateView() {
728   return !IsPrintPreview();
729 }
730
731 void PdfViewWebPlugin::RotateView(blink::WebPlugin::RotationType type) {
732   DCHECK(CanRotateView());
733
734   switch (type) {
735     case blink::WebPlugin::RotationType::k90Clockwise:
736       engine_->RotateClockwise();
737       break;
738     case blink::WebPlugin::RotationType::k90Counterclockwise:
739       engine_->RotateCounterclockwise();
740       break;
741   }
742 }
743
744 bool PdfViewWebPlugin::ShouldDispatchImeEventsToPlugin() {
745   return true;
746 }
747
748 blink::WebTextInputType PdfViewWebPlugin::GetPluginTextInputType() {
749   return text_input_type_;
750 }
751
752 gfx::Rect PdfViewWebPlugin::GetPluginCaretBounds() {
753   return caret_rect_;
754 }
755
756 void PdfViewWebPlugin::ImeSetCompositionForPlugin(
757     const blink::WebString& text,
758     const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
759     const gfx::Range& /*replacement_range*/,
760     int /*selection_start*/,
761     int /*selection_end*/) {
762   composition_text_ = text;
763 }
764
765 void PdfViewWebPlugin::ImeCommitTextForPlugin(
766     const blink::WebString& text,
767     const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
768     const gfx::Range& /*replacement_range*/,
769     int /*relative_cursor_pos*/) {
770   HandleImeCommit(text);
771 }
772
773 void PdfViewWebPlugin::ImeFinishComposingTextForPlugin(
774     bool /*keep_selection*/) {
775   HandleImeCommit(composition_text_);
776 }
777
778 void PdfViewWebPlugin::ProposeDocumentLayout(const DocumentLayout& layout) {
779   base::Value::Dict message;
780   message.Set("type", "documentDimensions");
781   message.Set("width", layout.size().width());
782   message.Set("height", layout.size().height());
783   message.Set("layoutOptions", layout.options().ToValue());
784   base::Value::List page_dimensions;
785   for (size_t i = 0; i < layout.page_count(); ++i)
786     page_dimensions.Append(DictFromRect(layout.page_rect(i)));
787   message.Set("pageDimensions", std::move(page_dimensions));
788   client_->PostMessage(std::move(message));
789
790   // Reload the accessibility tree on layout changes because the relative page
791   // bounds are no longer valid.
792   if (layout.dirty() && accessibility_state_ == AccessibilityState::kLoaded)
793     LoadAccessibility();
794 }
795
796 void PdfViewWebPlugin::Invalidate(const gfx::Rect& rect) {
797   if (in_paint_) {
798     deferred_invalidates_.push_back(rect);
799     return;
800   }
801
802   gfx::Rect offset_rect = rect + available_area_.OffsetFromOrigin();
803   paint_manager_.InvalidateRect(offset_rect);
804 }
805
806 void PdfViewWebPlugin::DidScroll(const gfx::Vector2d& offset) {
807   if (!image_data_.drawsNothing())
808     paint_manager_.ScrollRect(available_area_, offset);
809 }
810
811 void PdfViewWebPlugin::ScrollToX(int x_screen_coords) {
812   const float x_scroll_pos = x_screen_coords / device_scale_;
813
814   base::Value::Dict message;
815   message.Set("type", "setScrollPosition");
816   message.Set("x", static_cast<double>(x_scroll_pos));
817   client_->PostMessage(std::move(message));
818 }
819
820 void PdfViewWebPlugin::ScrollToY(int y_screen_coords) {
821   const float y_scroll_pos = y_screen_coords / device_scale_;
822
823   base::Value::Dict message;
824   message.Set("type", "setScrollPosition");
825   message.Set("y", static_cast<double>(y_scroll_pos));
826   client_->PostMessage(std::move(message));
827 }
828
829 void PdfViewWebPlugin::ScrollBy(const gfx::Vector2d& delta) {
830   const float x_delta = delta.x() / device_scale_;
831   const float y_delta = delta.y() / device_scale_;
832
833   base::Value::Dict message;
834   message.Set("type", "scrollBy");
835   message.Set("x", static_cast<double>(x_delta));
836   message.Set("y", static_cast<double>(y_delta));
837   client_->PostMessage(std::move(message));
838 }
839
840 void PdfViewWebPlugin::ScrollToPage(int page) {
841   if (!engine_ || engine_->GetNumberOfPages() == 0)
842     return;
843
844   base::Value::Dict message;
845   message.Set("type", "goToPage");
846   message.Set("page", page);
847   client_->PostMessage(std::move(message));
848 }
849
850 void PdfViewWebPlugin::NavigateTo(const std::string& url,
851                                   WindowOpenDisposition disposition) {
852   base::Value::Dict message;
853   message.Set("type", "navigate");
854   message.Set("url", url);
855   message.Set("disposition", static_cast<int>(disposition));
856   client_->PostMessage(std::move(message));
857 }
858
859 void PdfViewWebPlugin::NavigateToDestination(int page,
860                                              const float* x,
861                                              const float* y,
862                                              const float* zoom) {
863   base::Value::Dict message;
864   message.Set("type", "navigateToDestination");
865   message.Set("page", page);
866   if (x)
867     message.Set("x", static_cast<double>(*x));
868   if (y)
869     message.Set("y", static_cast<double>(*y));
870   if (zoom)
871     message.Set("zoom", static_cast<double>(*zoom));
872   client_->PostMessage(std::move(message));
873 }
874
875 void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) {
876   cursor_type_ = new_cursor_type;
877 }
878
879 void PdfViewWebPlugin::UpdateTickMarks(
880     const std::vector<gfx::Rect>& tickmarks) {
881   tickmarks_ = tickmarks;
882 }
883
884 void PdfViewWebPlugin::NotifyNumberOfFindResultsChanged(int total,
885                                                         bool final_result) {
886   // We don't want to spam the renderer with too many updates to the number of
887   // find results. Don't send an update if we sent one too recently. If it's the
888   // final update, we always send it though.
889   if (recently_sent_find_update_ && !final_result)
890     return;
891
892   // After stopping search and setting `find_identifier_` to -1 there still may
893   // be a NotifyNumberOfFindResultsChanged notification pending from engine.
894   // Just ignore them.
895   if (find_identifier_ != -1) {
896     client_->ReportFindInPageMatchCount(find_identifier_, total, final_result);
897   }
898
899   client_->ReportFindInPageTickmarks(tickmarks_);
900
901   if (final_result)
902     return;
903
904   recently_sent_find_update_ = true;
905   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
906       FROM_HERE,
907       base::BindOnce(&PdfViewWebPlugin::ResetRecentlySentFindUpdate,
908                      weak_factory_.GetWeakPtr()),
909       kFindResultCooldown);
910 }
911
912 void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index,
913                                                        bool final_result) {
914   if (find_identifier_ == -1 || !client_->PluginContainer())
915     return;
916
917   DCHECK_GE(current_find_index, -1);
918   client_->ReportFindInPageSelection(find_identifier_, current_find_index + 1,
919                                      final_result);
920 }
921
922 void PdfViewWebPlugin::NotifyTouchSelectionOccurred() {
923   base::Value::Dict message;
924   message.Set("type", "touchSelectionOccurred");
925   client_->PostMessage(std::move(message));
926 }
927
928 void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
929   caret_rect_ = gfx::ScaleToEnclosingRect(
930       caret_rect + available_area_.OffsetFromOrigin(), device_to_css_scale_);
931 }
932
933 void PdfViewWebPlugin::GetDocumentPassword(
934     base::OnceCallback<void(const std::string&)> callback) {
935   DCHECK(password_callback_.is_null());
936   password_callback_ = std::move(callback);
937
938   base::Value::Dict message;
939   message.Set("type", "getPassword");
940   client_->PostMessage(std::move(message));
941 }
942
943 void PdfViewWebPlugin::Beep() {
944   base::Value::Dict message;
945   message.Set("type", "beep");
946   client_->PostMessage(std::move(message));
947 }
948
949 void PdfViewWebPlugin::Alert(const std::string& message) {
950   client_->Alert(blink::WebString::FromUTF8(message));
951 }
952
953 bool PdfViewWebPlugin::Confirm(const std::string& message) {
954   return client_->Confirm(blink::WebString::FromUTF8(message));
955 }
956
957 std::string PdfViewWebPlugin::Prompt(const std::string& question,
958                                      const std::string& default_answer) {
959   return client_
960       ->Prompt(blink::WebString::FromUTF8(question),
961                blink::WebString::FromUTF8(default_answer))
962       .Utf8();
963 }
964
965 std::string PdfViewWebPlugin::GetURL() {
966   return url_;
967 }
968
969 void PdfViewWebPlugin::LoadUrl(base::StringPiece url,
970                                LoadUrlCallback callback) {
971   UrlRequest request;
972   request.url = std::string(url);
973   request.method = "GET";
974   request.ignore_redirects = true;
975
976   auto loader = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
977   UrlLoader* raw_loader = loader.get();
978   raw_loader->Open(request,
979                    base::BindOnce(std::move(callback), std::move(loader)));
980 }
981
982 void PdfViewWebPlugin::Email(const std::string& to,
983                              const std::string& cc,
984                              const std::string& bcc,
985                              const std::string& subject,
986                              const std::string& body) {
987   base::Value::Dict message;
988   message.Set("type", "email");
989   message.Set("to", base::EscapeUrlEncodedData(to, false));
990   message.Set("cc", base::EscapeUrlEncodedData(cc, false));
991   message.Set("bcc", base::EscapeUrlEncodedData(bcc, false));
992   message.Set("subject", base::EscapeUrlEncodedData(subject, false));
993   message.Set("body", base::EscapeUrlEncodedData(body, false));
994   client_->PostMessage(std::move(message));
995 }
996
997 void PdfViewWebPlugin::Print() {
998   if (!engine_)
999     return;
1000
1001   const bool can_print =
1002       engine_->HasPermission(DocumentPermission::kPrintLowQuality) ||
1003       engine_->HasPermission(DocumentPermission::kPrintHighQuality);
1004   if (!can_print)
1005     return;
1006
1007   base::ThreadTaskRunnerHandle::Get()->PostTask(
1008       FROM_HERE, base::BindOnce(&PdfViewWebPlugin::OnInvokePrintDialog,
1009                                 weak_factory_.GetWeakPtr()));
1010 }
1011
1012 void PdfViewWebPlugin::SubmitForm(const std::string& url,
1013                                   const void* data,
1014                                   int length) {
1015   // `url` might be a relative URL. Resolve it against the document's URL.
1016   // TODO(crbug.com/1322928): Probably redundant with `Client::CompleteURL()`.
1017   GURL resolved_url = GURL(url_).Resolve(url);
1018   if (!resolved_url.is_valid())
1019     return;
1020
1021   UrlRequest request;
1022   request.url = resolved_url.spec();
1023   request.method = "POST";
1024   request.body.assign(static_cast<const char*>(data), length);
1025
1026   form_loader_ = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
1027   form_loader_->Open(request, base::BindOnce(&PdfViewWebPlugin::DidFormOpen,
1028                                              weak_factory_.GetWeakPtr()));
1029 }
1030
1031 void PdfViewWebPlugin::DidFormOpen(int32_t result) {
1032   // TODO(crbug.com/719344): Process response.
1033   LOG_IF(ERROR, result != kSuccess) << "DidFormOpen failed: " << result;
1034   form_loader_.reset();
1035 }
1036
1037 std::unique_ptr<UrlLoader> PdfViewWebPlugin::CreateUrlLoader() {
1038   if (full_frame_) {
1039     DidStartLoading();
1040
1041     // Disable save and print until the document is fully loaded, since they
1042     // would generate an incomplete document. This needs to be done each time
1043     // DidStartLoading() is called because that resets the content restrictions.
1044     SetContentRestrictions(kContentRestrictionSave | kContentRestrictionPrint);
1045   }
1046
1047   return std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
1048 }
1049
1050 std::vector<PDFEngine::Client::SearchStringResult>
1051 PdfViewWebPlugin::SearchString(const char16_t* string,
1052                                const char16_t* term,
1053                                bool case_sensitive) {
1054   base::i18n::RepeatingStringSearch searcher(
1055       /*find_this=*/term, /*in_this=*/string, case_sensitive);
1056   std::vector<SearchStringResult> results;
1057   int match_index;
1058   int match_length;
1059   while (searcher.NextMatchResult(match_index, match_length))
1060     results.push_back({.start_index = match_index, .length = match_length});
1061   return results;
1062 }
1063
1064 void PdfViewWebPlugin::DocumentHasUnsupportedFeature(
1065     const std::string& feature) {
1066   DCHECK(!feature.empty());
1067   std::string metric = base::StrCat({"PDF_Unsupported_", feature});
1068   if (unsupported_features_reported_.insert(metric).second)
1069     client_->RecordComputedAction(metric);
1070
1071   if (!full_frame_ || notified_browser_about_unsupported_feature_)
1072     return;
1073
1074   notified_browser_about_unsupported_feature_ = true;
1075   pdf_service_->HasUnsupportedFeature();
1076 }
1077
1078 void PdfViewWebPlugin::DocumentLoadProgress(uint32_t available,
1079                                             uint32_t doc_size) {
1080   double progress = 0.0;
1081   if (doc_size > 0) {
1082     progress = 100.0 * static_cast<double>(available) / doc_size;
1083   } else {
1084     // Use heuristics when the document size is unknown.
1085     // Progress logarithmically from 0 to 100M.
1086     static const double kFactor = std::log(100'000'000.0) / 100.0;
1087     if (available > 0)
1088       progress =
1089           std::min(std::log(static_cast<double>(available)) / kFactor, 100.0);
1090   }
1091
1092   // DocumentLoadComplete() will send the 100% load progress.
1093   if (progress >= 100)
1094     return;
1095
1096   // Avoid sending too many progress messages over PostMessage.
1097   if (progress <= last_progress_sent_ + 1)
1098     return;
1099
1100   SendLoadingProgress(progress);
1101 }
1102
1103 void PdfViewWebPlugin::FormFieldFocusChange(PDFEngine::FocusFieldType type) {
1104   base::Value::Dict message;
1105   message.Set("type", "formFocusChange");
1106   message.Set("focused", type != PDFEngine::FocusFieldType::kNoFocus);
1107   client_->PostMessage(std::move(message));
1108
1109   text_input_type_ = type == PDFEngine::FocusFieldType::kText
1110                          ? blink::WebTextInputType::kWebTextInputTypeText
1111                          : blink::WebTextInputType::kWebTextInputTypeNone;
1112   client_->UpdateTextInputState();
1113 }
1114
1115 bool PdfViewWebPlugin::IsPrintPreview() const {
1116   return is_print_preview_;
1117 }
1118
1119 SkColor PdfViewWebPlugin::GetBackgroundColor() const {
1120   return background_color_;
1121 }
1122
1123 void PdfViewWebPlugin::SetIsSelecting(bool is_selecting) {
1124   base::Value::Dict message;
1125   message.Set("type", "setIsSelecting");
1126   message.Set("isSelecting", is_selecting);
1127   client_->PostMessage(std::move(message));
1128 }
1129
1130 void PdfViewWebPlugin::EnteredEditMode() {
1131   edit_mode_ = true;
1132   pdf_service_->SetPluginCanSave(true);
1133
1134   base::Value::Dict message;
1135   message.Set("type", "setIsEditing");
1136   client_->PostMessage(std::move(message));
1137 }
1138
1139 void PdfViewWebPlugin::DocumentFocusChanged(bool document_has_focus) {
1140   base::Value::Dict message;
1141   message.Set("type", "documentFocusChanged");
1142   message.Set("hasFocus", document_has_focus);
1143   client_->PostMessage(std::move(message));
1144 }
1145
1146 void PdfViewWebPlugin::SetSelectedText(const std::string& selected_text) {
1147   selected_text_ = blink::WebString::FromUTF8(selected_text);
1148   client_->TextSelectionChanged(selected_text_, /*offset=*/0,
1149                                 gfx::Range(0, selected_text_.length()));
1150 }
1151
1152 void PdfViewWebPlugin::SetLinkUnderCursor(
1153     const std::string& link_under_cursor) {
1154   link_under_cursor_ = link_under_cursor;
1155 }
1156
1157 bool PdfViewWebPlugin::IsValidLink(const std::string& url) {
1158   return base::Value(url).is_string();
1159 }
1160
1161 void PdfViewWebPlugin::SetCaretPosition(const gfx::PointF& position) {
1162   engine_->SetCaretPosition(FrameToPdfCoordinates(position));
1163 }
1164
1165 void PdfViewWebPlugin::MoveRangeSelectionExtent(const gfx::PointF& extent) {
1166   engine_->MoveRangeSelectionExtent(FrameToPdfCoordinates(extent));
1167 }
1168
1169 void PdfViewWebPlugin::SetSelectionBounds(const gfx::PointF& base,
1170                                           const gfx::PointF& extent) {
1171   engine_->SetSelectionBounds(FrameToPdfCoordinates(base),
1172                               FrameToPdfCoordinates(extent));
1173 }
1174
1175 bool PdfViewWebPlugin::IsValid() const {
1176   return client_->HasFrame();
1177 }
1178
1179 blink::WebURL PdfViewWebPlugin::CompleteURL(
1180     const blink::WebString& partial_url) const {
1181   DCHECK(IsValid());
1182   return client_->CompleteURL(partial_url);
1183 }
1184
1185 net::SiteForCookies PdfViewWebPlugin::SiteForCookies() const {
1186   DCHECK(IsValid());
1187   return client_->SiteForCookies();
1188 }
1189
1190 void PdfViewWebPlugin::SetReferrerForRequest(
1191     blink::WebURLRequest& request,
1192     const blink::WebURL& referrer_url) {
1193   client_->SetReferrerForRequest(request, referrer_url);
1194 }
1195
1196 std::unique_ptr<blink::WebAssociatedURLLoader>
1197 PdfViewWebPlugin::CreateAssociatedURLLoader(
1198     const blink::WebAssociatedURLLoaderOptions& options) {
1199   return client_->CreateAssociatedURLLoader(options);
1200 }
1201
1202 void PdfViewWebPlugin::OnMessage(const base::Value::Dict& message) {
1203   using MessageHandler = void (PdfViewWebPlugin::*)(const base::Value::Dict&);
1204
1205   // Settings this as const instead of constexpr to workaround a bug
1206   // in GCC, that will try to reinterpret_cast the method pointers.
1207   // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105996
1208   // TODO(crbug.com/1302059): make it constexpr again when we get rid
1209   // of PdfViewPluginBase
1210   static const auto kMessageHandlers =
1211       base::MakeFixedFlatMap<base::StringPiece, MessageHandler>({
1212           {"displayAnnotations",
1213            &PdfViewWebPlugin::HandleDisplayAnnotationsMessage},
1214           {"getNamedDestination",
1215            &PdfViewWebPlugin::HandleGetNamedDestinationMessage},
1216           {"getPasswordComplete",
1217            &PdfViewWebPlugin::HandleGetPasswordCompleteMessage},
1218           {"getSelectedText", &PdfViewWebPlugin::HandleGetSelectedTextMessage},
1219           {"getThumbnail", &PdfViewWebPlugin::HandleGetThumbnailMessage},
1220           {"print", &PdfViewWebPlugin::HandlePrintMessage},
1221           {"loadPreviewPage", &PdfViewWebPlugin::HandleLoadPreviewPageMessage},
1222           {"resetPrintPreviewMode",
1223            &PdfViewWebPlugin::HandleResetPrintPreviewModeMessage},
1224           {"rotateClockwise", &PdfViewWebPlugin::HandleRotateClockwiseMessage},
1225           {"rotateCounterclockwise",
1226            &PdfViewWebPlugin::HandleRotateCounterclockwiseMessage},
1227           {"save", &PdfViewWebPlugin::HandleSaveMessage},
1228           {"saveAttachment", &PdfViewWebPlugin::HandleSaveAttachmentMessage},
1229           {"selectAll", &PdfViewWebPlugin::HandleSelectAllMessage},
1230           {"setBackgroundColor",
1231            &PdfViewWebPlugin::HandleSetBackgroundColorMessage},
1232           {"setPresentationMode",
1233            &PdfViewWebPlugin::HandleSetPresentationModeMessage},
1234           {"setTwoUpView", &PdfViewWebPlugin::HandleSetTwoUpViewMessage},
1235           {"stopScrolling", &PdfViewWebPlugin::HandleStopScrollingMessage},
1236           {"viewport", &PdfViewWebPlugin::HandleViewportMessage},
1237       });
1238
1239   MessageHandler handler = kMessageHandlers.at(*message.FindString("type"));
1240   (this->*handler)(message);
1241 }
1242
1243 void PdfViewWebPlugin::HandleDisplayAnnotationsMessage(
1244     const base::Value::Dict& message) {
1245   engine_->DisplayAnnotations(message.FindBool("display").value());
1246 }
1247
1248 void PdfViewWebPlugin::HandleGetNamedDestinationMessage(
1249     const base::Value::Dict& message) {
1250   absl::optional<PDFEngine::NamedDestination> named_destination =
1251       engine_->GetNamedDestination(*message.FindString("namedDestination"));
1252
1253   const int page_number = named_destination.has_value()
1254                               ? base::checked_cast<int>(named_destination->page)
1255                               : -1;
1256
1257   base::Value::Dict reply =
1258       PrepareReplyMessage("getNamedDestinationReply", message);
1259   reply.Set("pageNumber", page_number);
1260
1261   if (named_destination.has_value() && !named_destination->view.empty()) {
1262     std::ostringstream view_stream;
1263     view_stream << named_destination->view;
1264     if (named_destination->xyz_params.empty()) {
1265       for (unsigned long i = 0; i < named_destination->num_params; ++i)
1266         view_stream << "," << named_destination->params[i];
1267     } else {
1268       view_stream << "," << named_destination->xyz_params;
1269     }
1270
1271     reply.Set("namedDestinationView", view_stream.str());
1272   }
1273
1274   client_->PostMessage(std::move(reply));
1275 }
1276
1277 void PdfViewWebPlugin::HandleGetPasswordCompleteMessage(
1278     const base::Value::Dict& message) {
1279   DCHECK(password_callback_);
1280   std::move(password_callback_).Run(*message.FindString("password"));
1281 }
1282
1283 void PdfViewWebPlugin::HandleGetSelectedTextMessage(
1284     const base::Value::Dict& message) {
1285   // Always return unix newlines to JavaScript.
1286   std::string selected_text;
1287   base::RemoveChars(engine_->GetSelectedText(), "\r", &selected_text);
1288
1289   base::Value::Dict reply =
1290       PrepareReplyMessage("getSelectedTextReply", message);
1291   reply.Set("selectedText", selected_text);
1292   client_->PostMessage(std::move(reply));
1293 }
1294
1295 void PdfViewWebPlugin::HandleGetThumbnailMessage(
1296     const base::Value::Dict& message) {
1297   const int page_index = message.FindInt("page").value();
1298   base::Value::Dict reply = PrepareReplyMessage("getThumbnailReply", message);
1299
1300   engine_->RequestThumbnail(
1301       page_index, device_scale_,
1302       base::BindOnce(&PdfViewWebPlugin::SendThumbnail,
1303                      weak_factory_.GetWeakPtr(), std::move(reply)));
1304 }
1305
1306 void PdfViewWebPlugin::HandlePrintMessage(
1307     const base::Value::Dict& /*message*/) {
1308   Print();
1309 }
1310
1311 void PdfViewWebPlugin::HandleRotateClockwiseMessage(
1312     const base::Value::Dict& /*message*/) {
1313   engine_->RotateClockwise();
1314 }
1315
1316 void PdfViewWebPlugin::HandleRotateCounterclockwiseMessage(
1317     const base::Value::Dict& /*message*/) {
1318   engine_->RotateCounterclockwise();
1319 }
1320
1321 void PdfViewWebPlugin::HandleSaveAttachmentMessage(
1322     const base::Value::Dict& message) {
1323   const int index = message.FindInt("attachmentIndex").value();
1324
1325   const std::vector<DocumentAttachmentInfo>& list =
1326       engine_->GetDocumentAttachmentInfoList();
1327   DCHECK_GE(index, 0);
1328   DCHECK_LT(static_cast<size_t>(index), list.size());
1329   DCHECK(list[index].is_readable);
1330   DCHECK(IsSaveDataSizeValid(list[index].size_bytes));
1331
1332   std::vector<uint8_t> data = engine_->GetAttachmentData(index);
1333   base::Value data_to_save(
1334       IsSaveDataSizeValid(data.size()) ? data : std::vector<uint8_t>());
1335
1336   base::Value::Dict reply = PrepareReplyMessage("saveAttachmentReply", message);
1337   reply.Set("dataToSave", std::move(data_to_save));
1338   client_->PostMessage(std::move(reply));
1339 }
1340
1341 void PdfViewWebPlugin::HandleSaveMessage(const base::Value::Dict& message) {
1342   const std::string& token = *message.FindString("token");
1343   int request_type = message.FindInt("saveRequestType").value();
1344   DCHECK_GE(request_type, static_cast<int>(SaveRequestType::kAnnotation));
1345   DCHECK_LE(request_type, static_cast<int>(SaveRequestType::kEdited));
1346
1347   switch (static_cast<SaveRequestType>(request_type)) {
1348     case SaveRequestType::kAnnotation:
1349 #if BUILDFLAG(ENABLE_INK)
1350       // In annotation mode, assume the user will make edits and prefer saving
1351       // using the plugin data.
1352       pdf_service_->SetPluginCanSave(true);
1353       SaveToBuffer(token);
1354 #else
1355       NOTREACHED();
1356 #endif  // BUILDFLAG(ENABLE_INK)
1357       break;
1358     case SaveRequestType::kOriginal:
1359       pdf_service_->SetPluginCanSave(false);
1360       SaveToFile(token);
1361       pdf_service_->SetPluginCanSave(edit_mode_);
1362       break;
1363     case SaveRequestType::kEdited:
1364       SaveToBuffer(token);
1365       break;
1366   }
1367 }
1368
1369 void PdfViewWebPlugin::HandleSelectAllMessage(
1370     const base::Value::Dict& /*message*/) {
1371   engine_->SelectAll();
1372 }
1373
1374 void PdfViewWebPlugin::HandleSetBackgroundColorMessage(
1375     const base::Value::Dict& message) {
1376   background_color_ =
1377       base::checked_cast<SkColor>(message.FindDouble("color").value());
1378 }
1379
1380 void PdfViewWebPlugin::HandleSetPresentationModeMessage(
1381     const base::Value::Dict& message) {
1382   engine_->SetReadOnly(message.FindBool("enablePresentationMode").value());
1383 }
1384
1385 void PdfViewWebPlugin::HandleSetTwoUpViewMessage(
1386     const base::Value::Dict& message) {
1387   engine_->SetDocumentLayout(message.FindBool("enableTwoUpView").value()
1388                                  ? DocumentLayout::PageSpread::kTwoUpOdd
1389                                  : DocumentLayout::PageSpread::kOneUp);
1390 }
1391
1392 void PdfViewWebPlugin::HandleStopScrollingMessage(
1393     const base::Value::Dict& /*message*/) {
1394   stop_scrolling_ = true;
1395 }
1396
1397 void PdfViewWebPlugin::HandleViewportMessage(const base::Value::Dict& message) {
1398   const base::Value::Dict* layout_options_value =
1399       message.FindDict("layoutOptions");
1400   if (layout_options_value) {
1401     DocumentLayout::Options layout_options;
1402     layout_options.FromValue(*layout_options_value);
1403
1404     ui_direction_ = layout_options.direction();
1405
1406     // TODO(crbug.com/1013800): Eliminate need to get document size from here.
1407     document_size_ = engine_->ApplyDocumentLayout(layout_options);
1408
1409     OnGeometryChanged(zoom_, device_scale_);
1410     if (!document_size_.IsEmpty())
1411       paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1412
1413     // Send 100% loading progress only after initial layout negotiated.
1414     if (last_progress_sent_ < 100 &&
1415         document_load_state_ == DocumentLoadState::kComplete) {
1416       SendLoadingProgress(/*percentage=*/100);
1417     }
1418   }
1419
1420   gfx::Vector2dF scroll_offset(*message.FindDouble("xOffset"),
1421                                *message.FindDouble("yOffset"));
1422   double new_zoom = *message.FindDouble("zoom");
1423   const PinchPhase pinch_phase =
1424       static_cast<PinchPhase>(*message.FindInt("pinchPhase"));
1425
1426   received_viewport_message_ = true;
1427   stop_scrolling_ = false;
1428   const double zoom_ratio = new_zoom / zoom_;
1429
1430   if (pinch_phase == PinchPhase::kStart) {
1431     scroll_offset_at_last_raster_ = scroll_offset;
1432     last_bitmap_smaller_ = false;
1433     needs_reraster_ = false;
1434     return;
1435   }
1436
1437   // When zooming in, we set a layer transform to avoid unneeded rerasters.
1438   // Also, if we're zooming out and the last time we rerastered was when
1439   // we were even further zoomed out (i.e. we pinch zoomed in and are now
1440   // pinch zooming back out in the same gesture), we update the layer
1441   // transform instead of rerastering.
1442   if (pinch_phase == PinchPhase::kUpdateZoomIn ||
1443       (pinch_phase == PinchPhase::kUpdateZoomOut && zoom_ratio > 1.0)) {
1444     // Get the coordinates of the center of the pinch gesture.
1445     const double pinch_x = *message.FindDouble("pinchX");
1446     const double pinch_y = *message.FindDouble("pinchY");
1447     gfx::Point pinch_center(pinch_x, pinch_y);
1448
1449     // Get the pinch vector which represents the panning caused by the change in
1450     // pinch center between the start and the end of the gesture.
1451     const double pinch_vector_x = *message.FindDouble("pinchVectorX");
1452     const double pinch_vector_y = *message.FindDouble("pinchVectorY");
1453     gfx::Vector2d pinch_vector =
1454         gfx::Vector2d(pinch_vector_x * zoom_ratio, pinch_vector_y * zoom_ratio);
1455
1456     gfx::Vector2d scroll_delta;
1457     // If the rendered document doesn't fill the display area we will
1458     // use `paint_offset` to anchor the paint vertically into the same place.
1459     // We use the scroll bars instead of the pinch vector to get the actual
1460     // position on screen of the paint.
1461     gfx::Vector2d paint_offset;
1462
1463     if (plugin_rect_.width() > GetDocumentPixelWidth() * zoom_ratio) {
1464       // We want to keep the paint in the middle but it must stay in the same
1465       // position relative to the scroll bars.
1466       paint_offset = gfx::Vector2d(0, (1 - zoom_ratio) * pinch_center.y());
1467       scroll_delta = gfx::Vector2d(
1468           0,
1469           (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
1470
1471       pinch_vector = gfx::Vector2d();
1472       last_bitmap_smaller_ = true;
1473     } else if (last_bitmap_smaller_) {
1474       // When the document width covers the display area's width, we will anchor
1475       // the scroll bars disregarding where the actual pinch certer is.
1476       pinch_center = gfx::Point((plugin_rect_.width() / device_scale_) / 2,
1477                                 (plugin_rect_.height() / device_scale_) / 2);
1478       const double zoom_when_doc_covers_plugin_width =
1479           zoom_ * plugin_rect_.width() / GetDocumentPixelWidth();
1480       paint_offset = gfx::Vector2d(
1481           (1 - new_zoom / zoom_when_doc_covers_plugin_width) * pinch_center.x(),
1482           (1 - zoom_ratio) * pinch_center.y());
1483       pinch_vector = gfx::Vector2d();
1484       scroll_delta = gfx::Vector2d(
1485           (scroll_offset.x() - scroll_offset_at_last_raster_.x() * zoom_ratio),
1486           (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
1487     }
1488
1489     paint_manager_.SetTransform(zoom_ratio, pinch_center,
1490                                 pinch_vector + paint_offset + scroll_delta,
1491                                 true);
1492     needs_reraster_ = false;
1493     return;
1494   }
1495
1496   if (pinch_phase == PinchPhase::kUpdateZoomOut ||
1497       pinch_phase == PinchPhase::kEnd) {
1498     // We reraster on pinch zoom out in order to solve the invalid regions
1499     // that appear after zooming out.
1500     // On pinch end the scale is again 1.f and we request a reraster
1501     // in the new position.
1502     paint_manager_.ClearTransform();
1503     last_bitmap_smaller_ = false;
1504     needs_reraster_ = true;
1505
1506     // If we're rerastering due to zooming out, we need to update the scroll
1507     // offset for the last raster, in case the user continues the gesture by
1508     // zooming in.
1509     scroll_offset_at_last_raster_ = scroll_offset;
1510   }
1511
1512   // Bound the input parameters.
1513   new_zoom = std::max(kMinZoom, new_zoom);
1514   DCHECK(message.FindBool("userInitiated").has_value());
1515
1516   double old_zoom = zoom_;
1517   zoom_ = new_zoom;
1518
1519   OnGeometryChanged(old_zoom, device_scale_);
1520   if (!document_size_.IsEmpty())
1521     paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1522
1523   UpdateScroll(GetScrollPositionFromOffset(scroll_offset));
1524 }
1525
1526 void PdfViewWebPlugin::SaveToBuffer(const std::string& token) {
1527   engine_->KillFormFocus();
1528
1529   base::Value::Dict message;
1530   message.Set("type", "saveData");
1531   message.Set("token", token);
1532   message.Set("fileName", GetFileNameForSaveFromUrl(url_));
1533
1534   // Expose `edit_mode_` state for integration testing.
1535   message.Set("editModeForTesting", edit_mode_);
1536
1537   base::Value data_to_save;
1538   if (edit_mode_) {
1539     base::Value::BlobStorage data = engine_->GetSaveData();
1540     if (IsSaveDataSizeValid(data.size()))
1541       data_to_save = base::Value(std::move(data));
1542   } else {
1543 #if BUILDFLAG(ENABLE_INK)
1544     uint32_t length = engine_->GetLoadedByteSize();
1545     if (IsSaveDataSizeValid(length)) {
1546       base::Value::BlobStorage data(length);
1547       if (engine_->ReadLoadedBytes(length, data.data()))
1548         data_to_save = base::Value(std::move(data));
1549     }
1550 #else
1551     NOTREACHED();
1552 #endif  // BUILDFLAG(ENABLE_INK)
1553   }
1554
1555   message.Set("dataToSave", std::move(data_to_save));
1556   client_->PostMessage(std::move(message));
1557 }
1558
1559 void PdfViewWebPlugin::SaveToFile(const std::string& token) {
1560   engine_->KillFormFocus();
1561
1562   base::Value::Dict message;
1563   message.Set("type", "consumeSaveToken");
1564   message.Set("token", token);
1565   client_->PostMessage(std::move(message));
1566
1567   // TODO(crbug.com/1302059): Is there a good reason to null-terminate here?
1568   pdf_service_->SaveUrlAs(GURL(url_.c_str()),
1569                           network::mojom::ReferrerPolicy::kDefault);
1570 }
1571
1572 void PdfViewWebPlugin::InvalidatePluginContainer() {
1573   client_->Invalidate();
1574 }
1575
1576 void PdfViewWebPlugin::OnPaint(const std::vector<gfx::Rect>& paint_rects,
1577                                std::vector<PaintReadyRect>& ready,
1578                                std::vector<gfx::Rect>& pending) {
1579   base::AutoReset<bool> auto_reset_in_paint(&in_paint_, true);
1580   DoPaint(paint_rects, ready, pending);
1581 }
1582
1583 gfx::PointF PdfViewWebPlugin::GetScrollPositionFromOffset(
1584     const gfx::Vector2dF& scroll_offset) const {
1585   gfx::PointF scroll_origin;
1586
1587   // TODO(crbug.com/1140374): Right-to-left scrolling currently is not
1588   // compatible with the PDF viewer's sticky "scroller" element.
1589   if (ui_direction_ == base::i18n::RIGHT_TO_LEFT && IsPrintPreview()) {
1590     scroll_origin.set_x(
1591         std::max(document_size_.width() * static_cast<float>(zoom_) -
1592                      plugin_dip_size_.width(),
1593                  0.0f));
1594   }
1595
1596   return scroll_origin + scroll_offset;
1597 }
1598
1599 void PdfViewWebPlugin::DoPaint(const std::vector<gfx::Rect>& paint_rects,
1600                                std::vector<PaintReadyRect>& ready,
1601                                std::vector<gfx::Rect>& pending) {
1602   if (image_data_.drawsNothing()) {
1603     DCHECK(plugin_rect_.IsEmpty());
1604     return;
1605   }
1606
1607   PrepareForFirstPaint(ready);
1608
1609   if (!received_viewport_message_ || !needs_reraster_)
1610     return;
1611
1612   engine_->PrePaint();
1613
1614   std::vector<gfx::Rect> ready_rects;
1615   for (const gfx::Rect& paint_rect : paint_rects) {
1616     // Intersect with plugin area since there could be pending invalidates from
1617     // when the plugin area was larger.
1618     gfx::Rect rect =
1619         gfx::IntersectRects(paint_rect, gfx::Rect(plugin_rect_.size()));
1620     if (rect.IsEmpty())
1621       continue;
1622
1623     // Paint the rendering of the PDF document.
1624     gfx::Rect pdf_rect = gfx::IntersectRects(rect, available_area_);
1625     if (!pdf_rect.IsEmpty()) {
1626       pdf_rect.Offset(-available_area_.x(), 0);
1627
1628       std::vector<gfx::Rect> pdf_ready;
1629       std::vector<gfx::Rect> pdf_pending;
1630       engine_->Paint(pdf_rect, image_data_, pdf_ready, pdf_pending);
1631       for (gfx::Rect& ready_rect : pdf_ready) {
1632         ready_rect.Offset(available_area_.OffsetFromOrigin());
1633         ready_rects.push_back(ready_rect);
1634       }
1635       for (gfx::Rect& pending_rect : pdf_pending) {
1636         pending_rect.Offset(available_area_.OffsetFromOrigin());
1637         pending.push_back(pending_rect);
1638       }
1639     }
1640
1641     // Ensure the region above the first page (if any) is filled;
1642     const int32_t first_page_ypos = 0 == engine_->GetNumberOfPages()
1643                                         ? 0
1644                                         : engine_->GetPageScreenRect(0).y();
1645     if (rect.y() < first_page_ypos) {
1646       gfx::Rect region = gfx::IntersectRects(
1647           rect, gfx::Rect(gfx::Size(plugin_rect_.width(), first_page_ypos)));
1648       image_data_.erase(GetBackgroundColor(), gfx::RectToSkIRect(region));
1649       ready_rects.push_back(region);
1650     }
1651
1652     // Ensure the background parts are filled.
1653     for (const BackgroundPart& background_part : background_parts_) {
1654       gfx::Rect intersection =
1655           gfx::IntersectRects(background_part.location, rect);
1656       if (!intersection.IsEmpty()) {
1657         image_data_.erase(background_part.color,
1658                           gfx::RectToSkIRect(intersection));
1659         ready_rects.push_back(intersection);
1660       }
1661     }
1662   }
1663
1664   engine_->PostPaint();
1665
1666   // TODO(crbug.com/1263614): Write pixels directly to the `SkSurface` in
1667   // `PaintManager`, rather than using an intermediate `SkBitmap` and `SkImage`.
1668   sk_sp<SkImage> painted_image = image_data_.asImage();
1669   for (const gfx::Rect& ready_rect : ready_rects)
1670     ready.emplace_back(ready_rect, painted_image);
1671
1672   InvalidateAfterPaintDone();
1673 }
1674
1675 void PdfViewWebPlugin::PrepareForFirstPaint(
1676     std::vector<PaintReadyRect>& ready) {
1677   if (!first_paint_)
1678     return;
1679
1680   // Fill the image data buffer with the background color.
1681   first_paint_ = false;
1682   image_data_.eraseColor(background_color_);
1683   ready.emplace_back(gfx::SkIRectToRect(image_data_.bounds()),
1684                      image_data_.asImage(), /*flush_now=*/true);
1685 }
1686
1687 void PdfViewWebPlugin::OnGeometryChanged(double old_zoom,
1688                                          float old_device_scale) {
1689   RecalculateAreas(old_zoom, old_device_scale);
1690
1691   if (accessibility_state_ == AccessibilityState::kLoaded)
1692     PrepareAndSetAccessibilityViewportInfo();
1693 }
1694
1695 void PdfViewWebPlugin::RecalculateAreas(double old_zoom,
1696                                         float old_device_scale) {
1697   if (zoom_ != old_zoom || device_scale_ != old_device_scale)
1698     engine_->ZoomUpdated(zoom_ * device_scale_);
1699
1700   available_area_ = gfx::Rect(plugin_rect_.size());
1701   int doc_width = GetDocumentPixelWidth();
1702   if (doc_width < available_area_.width()) {
1703     // Center the document horizontally inside the plugin rectangle.
1704     available_area_.Offset((plugin_rect_.width() - doc_width) / 2, 0);
1705     available_area_.set_width(doc_width);
1706   }
1707
1708   // The distance between top of the plugin and the bottom of the document in
1709   // pixels.
1710   int bottom_of_document = GetDocumentPixelHeight();
1711   if (bottom_of_document < plugin_rect_.height())
1712     available_area_.set_height(bottom_of_document);
1713
1714   CalculateBackgroundParts();
1715
1716   engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
1717   engine_->PluginSizeUpdated(available_area_.size());
1718 }
1719
1720 void PdfViewWebPlugin::CalculateBackgroundParts() {
1721   background_parts_.clear();
1722   int left_width = available_area_.x();
1723   int right_start = available_area_.right();
1724   int right_width = std::abs(plugin_rect_.width() - available_area_.right());
1725   int bottom = std::min(available_area_.bottom(), plugin_rect_.height());
1726
1727   // Note: we assume the display of the PDF document is always centered
1728   // horizontally, but not necessarily centered vertically.
1729   // Add the left rectangle.
1730   BackgroundPart part = {gfx::Rect(left_width, bottom), GetBackgroundColor()};
1731   if (!part.location.IsEmpty())
1732     background_parts_.push_back(part);
1733
1734   // Add the right rectangle.
1735   part.location = gfx::Rect(right_start, 0, right_width, bottom);
1736   if (!part.location.IsEmpty())
1737     background_parts_.push_back(part);
1738
1739   // Add the bottom rectangle.
1740   part.location = gfx::Rect(0, bottom, plugin_rect_.width(),
1741                             plugin_rect_.height() - bottom);
1742   if (!part.location.IsEmpty())
1743     background_parts_.push_back(part);
1744 }
1745
1746 int PdfViewWebPlugin::GetDocumentPixelWidth() const {
1747   return static_cast<int>(
1748       std::ceil(document_size_.width() * zoom_ * device_scale_));
1749 }
1750
1751 int PdfViewWebPlugin::GetDocumentPixelHeight() const {
1752   return static_cast<int>(
1753       std::ceil(document_size_.height() * zoom_ * device_scale_));
1754 }
1755
1756 void PdfViewWebPlugin::InvalidateAfterPaintDone() {
1757   if (deferred_invalidates_.empty())
1758     return;
1759
1760   base::ThreadTaskRunnerHandle::Get()->PostTask(
1761       FROM_HERE, base::BindOnce(&PdfViewWebPlugin::ClearDeferredInvalidates,
1762                                 weak_factory_.GetWeakPtr()));
1763 }
1764
1765 void PdfViewWebPlugin::ClearDeferredInvalidates() {
1766   DCHECK(!in_paint_);
1767   for (const gfx::Rect& rect : deferred_invalidates_)
1768     Invalidate(rect);
1769   deferred_invalidates_.clear();
1770 }
1771
1772 void PdfViewWebPlugin::UpdateSnapshot(sk_sp<SkImage> snapshot) {
1773   snapshot_ =
1774       cc::PaintImageBuilder::WithDefault()
1775           .set_image(std::move(snapshot), cc::PaintImage::GetNextContentId())
1776           .set_id(cc::PaintImage::GetNextId())
1777           .TakePaintImage();
1778   if (!plugin_rect_.IsEmpty())
1779     InvalidatePluginContainer();
1780 }
1781
1782 void PdfViewWebPlugin::UpdateScaledValues() {
1783   total_translate_ = snapshot_translate_;
1784
1785   if (viewport_to_dip_scale_ != 1.0f)
1786     total_translate_.Scale(1.0f / viewport_to_dip_scale_);
1787 }
1788
1789 void PdfViewWebPlugin::UpdateScale(float scale) {
1790   if (scale <= 0.0f) {
1791     NOTREACHED();
1792     return;
1793   }
1794
1795   viewport_to_dip_scale_ = scale;
1796   device_to_css_scale_ = 1.0f;
1797   UpdateScaledValues();
1798 }
1799
1800 void PdfViewWebPlugin::UpdateLayerTransform(float scale,
1801                                             const gfx::Vector2dF& translate) {
1802   snapshot_translate_ = translate;
1803   snapshot_scale_ = scale;
1804   UpdateScaledValues();
1805 }
1806
1807 void PdfViewWebPlugin::EnableAccessibility() {
1808   if (accessibility_state_ == AccessibilityState::kLoaded)
1809     return;
1810
1811   if (accessibility_state_ == AccessibilityState::kOff)
1812     accessibility_state_ = AccessibilityState::kPending;
1813
1814   if (document_load_state_ == DocumentLoadState::kComplete)
1815     LoadAccessibility();
1816 }
1817
1818 void PdfViewWebPlugin::HandleAccessibilityAction(
1819     const AccessibilityActionData& action_data) {
1820   engine_->HandleAccessibilityAction(action_data);
1821 }
1822
1823 base::WeakPtr<PdfViewPluginBase> PdfViewWebPlugin::GetWeakPtr() {
1824   return weak_factory_.GetWeakPtr();
1825 }
1826
1827 void PdfViewWebPlugin::OnPrintPreviewLoaded() {
1828   // Scroll location is retained across document loads in Print Preview, so
1829   // there's no need to override the scroll position by scrolling again.
1830   if (IsPreviewingPDF(print_preview_page_count_)) {
1831     SendPrintPreviewLoadedNotification();
1832   } else {
1833     DCHECK_EQ(0, print_preview_loaded_page_count_);
1834     print_preview_loaded_page_count_ = 1;
1835     engine_->AppendBlankPages(print_preview_page_count_);
1836     LoadNextPreviewPage();
1837   }
1838
1839   OnGeometryChanged(0, 0);
1840   if (!document_size_.IsEmpty())
1841     paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1842 }
1843
1844 void PdfViewWebPlugin::OnDocumentLoadComplete() {
1845   RecordDocumentMetrics();
1846
1847   SendAttachments();
1848   SendBookmarks();
1849   SendMetadata();
1850
1851   if (accessibility_state_ == AccessibilityState::kPending)
1852     LoadAccessibility();
1853 }
1854
1855 void PdfViewWebPlugin::SendLoadingProgress(double percentage) {
1856   DCHECK(percentage == -1 || (percentage >= 0 && percentage <= 100));
1857   last_progress_sent_ = percentage;
1858
1859   base::Value::Dict message;
1860   message.Set("type", "loadProgress");
1861   message.Set("progress", percentage);
1862   client_->PostMessage(std::move(message));
1863 }
1864
1865 void PdfViewWebPlugin::SetAccessibilityDocInfo(AccessibilityDocInfo doc_info) {
1866   pdf_accessibility_data_handler_->SetAccessibilityDocInfo(std::move(doc_info));
1867 }
1868
1869 void PdfViewWebPlugin::SetAccessibilityPageInfo(
1870     AccessibilityPageInfo page_info,
1871     std::vector<AccessibilityTextRunInfo> text_runs,
1872     std::vector<AccessibilityCharInfo> chars,
1873     AccessibilityPageObjects page_objects) {
1874   pdf_accessibility_data_handler_->SetAccessibilityPageInfo(
1875       std::move(page_info), std::move(text_runs), std::move(chars),
1876       std::move(page_objects));
1877 }
1878
1879 void PdfViewWebPlugin::SetAccessibilityViewportInfo(
1880     AccessibilityViewportInfo viewport_info) {
1881   pdf_accessibility_data_handler_->SetAccessibilityViewportInfo(
1882       std::move(viewport_info));
1883 }
1884
1885 void PdfViewWebPlugin::SetContentRestrictions(int content_restrictions) {
1886   pdf_service_->UpdateContentRestrictions(content_restrictions);
1887 }
1888
1889 void PdfViewWebPlugin::DidStartLoading() {
1890   if (did_call_start_loading_)
1891     return;
1892
1893   client_->DidStartLoading();
1894   did_call_start_loading_ = true;
1895 }
1896
1897 void PdfViewWebPlugin::DidStopLoading() {
1898   if (!did_call_start_loading_)
1899     return;
1900
1901   client_->DidStopLoading();
1902   did_call_start_loading_ = false;
1903 }
1904
1905 void PdfViewWebPlugin::NotifySelectionChanged(const gfx::PointF& left,
1906                                               int left_height,
1907                                               const gfx::PointF& right,
1908                                               int right_height) {
1909   pdf_service_->SelectionChanged(left, left_height, right, right_height);
1910 }
1911
1912 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1913 void PdfViewWebPlugin::UserMetricsRecordAction(const std::string& action) {
1914   client_->RecordComputedAction(action);
1915 }
1916
1917 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1918 PaintManager& PdfViewWebPlugin::paint_manager() {
1919   return paint_manager_;
1920 }
1921
1922 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1923 const gfx::Rect& PdfViewWebPlugin::available_area() const {
1924   return available_area_;
1925 }
1926
1927 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1928 double PdfViewWebPlugin::zoom() const {
1929   return zoom_;
1930 }
1931
1932 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1933 bool PdfViewWebPlugin::full_frame() const {
1934   return full_frame_;
1935 }
1936
1937 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1938 const gfx::Rect& PdfViewWebPlugin::plugin_rect() const {
1939   return plugin_rect_;
1940 }
1941
1942 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1943 float PdfViewWebPlugin::device_scale() const {
1944   return device_scale_;
1945 }
1946
1947 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1948 PdfViewPluginBase::DocumentLoadState PdfViewWebPlugin::document_load_state()
1949     const {
1950   return document_load_state_;
1951 }
1952
1953 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1954 void PdfViewWebPlugin::set_document_load_state(DocumentLoadState state) {
1955   document_load_state_ = state;
1956 }
1957
1958 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1959 PdfViewPluginBase::AccessibilityState PdfViewWebPlugin::accessibility_state()
1960     const {
1961   return accessibility_state_;
1962 }
1963
1964 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1965 void PdfViewWebPlugin::set_accessibility_state(AccessibilityState state) {
1966   accessibility_state_ = state;
1967 }
1968
1969 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1970 int32_t PdfViewWebPlugin::next_accessibility_page_index() const {
1971   return next_accessibility_page_index_;
1972 }
1973
1974 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1975 void PdfViewWebPlugin::increment_next_accessibility_page_index() {
1976   ++next_accessibility_page_index_;
1977 }
1978
1979 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1980 void PdfViewWebPlugin::reset_next_accessibility_page_index() {
1981   next_accessibility_page_index_ = 0;
1982 }
1983
1984 void PdfViewWebPlugin::OnViewportChanged(
1985     const gfx::Rect& new_plugin_rect_in_css_pixel,
1986     float new_device_scale) {
1987   DCHECK_GT(new_device_scale, 0.0f);
1988
1989   css_plugin_rect_ = new_plugin_rect_in_css_pixel;
1990
1991   if (new_device_scale == device_scale_ &&
1992       new_plugin_rect_in_css_pixel == plugin_rect_) {
1993     return;
1994   }
1995
1996   const float old_device_scale = device_scale_;
1997   device_scale_ = new_device_scale;
1998   plugin_rect_ = new_plugin_rect_in_css_pixel;
1999
2000   // TODO(crbug.com/1250173): We should try to avoid the downscaling in this
2001   // calculation, perhaps by migrating off `plugin_dip_size_`.
2002   plugin_dip_size_ = gfx::ScaleToEnclosingRect(new_plugin_rect_in_css_pixel,
2003                                                1.0f / new_device_scale)
2004                          .size();
2005
2006   paint_manager_.SetSize(plugin_rect_.size(), device_scale_);
2007
2008   // Initialize the image data buffer if the context size changes.
2009   const gfx::Size old_image_size = gfx::SkISizeToSize(image_data_.dimensions());
2010   const gfx::Size new_image_size =
2011       PaintManager::GetNewContextSize(old_image_size, plugin_rect_.size());
2012   if (new_image_size != old_image_size) {
2013     image_data_.allocPixels(
2014         SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(new_image_size)));
2015     first_paint_ = true;
2016   }
2017
2018   // Skip updating the geometry if the new image data buffer is empty.
2019   if (image_data_.drawsNothing())
2020     return;
2021
2022   OnGeometryChanged(zoom_, old_device_scale);
2023 }
2024
2025 bool PdfViewWebPlugin::SelectAll() {
2026   if (!CanEditText())
2027     return false;
2028
2029   engine_->SelectAll();
2030   return true;
2031 }
2032
2033 bool PdfViewWebPlugin::Cut() {
2034   if (!HasSelection() || !CanEditText())
2035     return false;
2036
2037   engine_->ReplaceSelection("");
2038   return true;
2039 }
2040
2041 bool PdfViewWebPlugin::Paste(const blink::WebString& value) {
2042   if (!CanEditText())
2043     return false;
2044
2045   engine_->ReplaceSelection(value.Utf8());
2046   return true;
2047 }
2048
2049 bool PdfViewWebPlugin::Undo() {
2050   if (!CanUndo())
2051     return false;
2052
2053   engine_->Undo();
2054   return true;
2055 }
2056
2057 bool PdfViewWebPlugin::Redo() {
2058   if (!CanRedo())
2059     return false;
2060
2061   engine_->Redo();
2062   return true;
2063 }
2064
2065 bool PdfViewWebPlugin::HandleWebInputEvent(const blink::WebInputEvent& event) {
2066   // Ignore user input in read-only mode.
2067   if (engine_->IsReadOnly())
2068     return false;
2069
2070   // `engine_` expects input events in device coordinates.
2071   std::unique_ptr<blink::WebInputEvent> transformed_event =
2072       ui::TranslateAndScaleWebInputEvent(
2073           event, gfx::Vector2dF(-available_area_.x() / device_scale_, 0),
2074           device_scale_);
2075
2076   const blink::WebInputEvent& event_to_handle =
2077       transformed_event ? *transformed_event : event;
2078
2079   if (engine_->HandleInputEvent(event_to_handle))
2080     return true;
2081
2082   // Middle click is used for scrolling and is handled by the container page.
2083   if (blink::WebInputEvent::IsMouseEventType(event_to_handle.GetType()) &&
2084       static_cast<const blink::WebMouseEvent&>(event_to_handle).button ==
2085           blink::WebPointerProperties::Button::kMiddle) {
2086     return false;
2087   }
2088
2089   // Return true for unhandled clicks so the plugin takes focus.
2090   return event_to_handle.GetType() == blink::WebInputEvent::Type::kMouseDown;
2091 }
2092
2093 void PdfViewWebPlugin::HandleImeCommit(const blink::WebString& text) {
2094   if (text.IsEmpty())
2095     return;
2096
2097   std::u16string text16 = text.Utf16();
2098   composition_text_.Reset();
2099
2100   size_t i = 0;
2101   for (base::i18n::UTF16CharIterator iterator(text16); iterator.Advance();) {
2102     blink::WebKeyboardEvent char_event(blink::WebInputEvent::Type::kChar,
2103                                        blink::WebInputEvent::kNoModifiers,
2104                                        ui::EventTimeForNow());
2105     char_event.windows_key_code = text16[i];
2106     char_event.native_key_code = text16[i];
2107
2108     for (const size_t char_start = i; i < iterator.array_pos(); ++i) {
2109       char_event.text[i - char_start] = text16[i];
2110       char_event.unmodified_text[i - char_start] = text16[i];
2111     }
2112
2113     blink::WebCoalescedInputEvent input_event(char_event, ui::LatencyInfo());
2114     ui::Cursor dummy_cursor_info;
2115     HandleInputEvent(input_event, &dummy_cursor_info);
2116   }
2117 }
2118
2119 void PdfViewWebPlugin::OnInvokePrintDialog() {
2120   client_->Print();
2121 }
2122
2123 void PdfViewWebPlugin::ResetRecentlySentFindUpdate() {
2124   recently_sent_find_update_ = false;
2125 }
2126
2127 void PdfViewWebPlugin::RecordDocumentMetrics() {
2128   if (!metrics_handler_)
2129     return;
2130
2131   metrics_handler_->RecordDocumentMetrics(engine_->GetDocumentMetadata());
2132   metrics_handler_->RecordAttachmentTypes(
2133       engine_->GetDocumentAttachmentInfoList());
2134 }
2135
2136 void PdfViewWebPlugin::SendAttachments() {
2137   const std::vector<DocumentAttachmentInfo>& attachment_infos =
2138       engine_->GetDocumentAttachmentInfoList();
2139   if (attachment_infos.empty())
2140     return;
2141
2142   base::Value::List attachments;
2143   for (const DocumentAttachmentInfo& attachment_info : attachment_infos) {
2144     // Send `size` as -1 to indicate that the attachment is too large to be
2145     // downloaded.
2146     const int size = attachment_info.size_bytes <= kMaximumSavedFileSize
2147                          ? static_cast<int>(attachment_info.size_bytes)
2148                          : -1;
2149
2150     base::Value::Dict attachment;
2151     attachment.Set("name", attachment_info.name);
2152     attachment.Set("size", size);
2153     attachment.Set("readable", attachment_info.is_readable);
2154     attachments.Append(std::move(attachment));
2155   }
2156
2157   base::Value::Dict message;
2158   message.Set("type", "attachments");
2159   message.Set("attachmentsData", std::move(attachments));
2160   client_->PostMessage(std::move(message));
2161 }
2162
2163 void PdfViewWebPlugin::SendBookmarks() {
2164   base::Value::List bookmarks = engine_->GetBookmarks();
2165   if (bookmarks.empty())
2166     return;
2167
2168   base::Value::Dict message;
2169   message.Set("type", "bookmarks");
2170   message.Set("bookmarksData", std::move(bookmarks));
2171   client_->PostMessage(std::move(message));
2172 }
2173
2174 void PdfViewWebPlugin::SendMetadata() {
2175   base::Value::Dict metadata;
2176   const DocumentMetadata& document_metadata = engine_->GetDocumentMetadata();
2177
2178   const std::string version = FormatPdfVersion(document_metadata.version);
2179   if (!version.empty())
2180     metadata.Set("version", version);
2181
2182   metadata.Set("fileSize", ui::FormatBytes(document_metadata.size_bytes));
2183
2184   metadata.Set("linearized", document_metadata.linearized);
2185
2186   if (!document_metadata.title.empty())
2187     metadata.Set("title", document_metadata.title);
2188
2189   if (!document_metadata.author.empty())
2190     metadata.Set("author", document_metadata.author);
2191
2192   if (!document_metadata.subject.empty())
2193     metadata.Set("subject", document_metadata.subject);
2194
2195   if (!document_metadata.keywords.empty())
2196     metadata.Set("keywords", document_metadata.keywords);
2197
2198   if (!document_metadata.creator.empty())
2199     metadata.Set("creator", document_metadata.creator);
2200
2201   if (!document_metadata.producer.empty())
2202     metadata.Set("producer", document_metadata.producer);
2203
2204   if (!document_metadata.creation_date.is_null()) {
2205     metadata.Set("creationDate", base::TimeFormatShortDateAndTime(
2206                                      document_metadata.creation_date));
2207   }
2208
2209   if (!document_metadata.mod_date.is_null()) {
2210     metadata.Set("modDate",
2211                  base::TimeFormatShortDateAndTime(document_metadata.mod_date));
2212   }
2213
2214   metadata.Set("pageSize", FormatPageSize(engine_->GetUniformPageSizePoints()));
2215
2216   metadata.Set("canSerializeDocument",
2217                IsSaveDataSizeValid(engine_->GetLoadedByteSize()));
2218
2219   base::Value::Dict message;
2220   message.Set("type", "metadata");
2221   message.Set("metadataData", std::move(metadata));
2222   client_->PostMessage(std::move(message));
2223 }
2224
2225 void PdfViewWebPlugin::HandleResetPrintPreviewModeMessage(
2226     const base::Value::Dict& message) {
2227   const std::string& url = *message.FindString("url");
2228   bool is_grayscale = message.FindBool("grayscale").value();
2229   int print_preview_page_count = message.FindInt("pageCount").value();
2230
2231   // For security reasons, crash if `url` is not for Print Preview.
2232   CHECK(IsPrintPreview());
2233   CHECK(IsPrintPreviewUrl(url));
2234
2235   DCHECK_GE(print_preview_page_count, 0);
2236
2237   int page_index = ExtractPrintPreviewPageIndex(url);
2238   if (IsPreviewingPDF(print_preview_page_count)) {
2239     DCHECK_EQ(page_index, kCompletePDFIndex);
2240   } else {
2241     DCHECK_GE(page_index, 0);
2242   }
2243
2244   print_preview_page_count_ = print_preview_page_count;
2245   print_preview_loaded_page_count_ = 0;
2246   url_ = url;
2247   preview_pages_info_ = base::queue<PreviewPageInfo>();
2248   preview_document_load_state_ = DocumentLoadState::kComplete;
2249   document_load_state_ = DocumentLoadState::kLoading;
2250   last_progress_sent_ = 0;
2251   LoadUrl(url_, base::BindOnce(&PdfViewWebPlugin::DidOpen,
2252                                weak_factory_.GetWeakPtr()));
2253   preview_engine_.reset();
2254
2255   // TODO(crbug.com/1237952): Figure out a more consistent way to preserve
2256   // engine settings across a Print Preview reset.
2257   engine_ = client_->CreateEngine(
2258       this, PDFiumFormFiller::ScriptOption::kNoJavaScript);
2259   engine_->ZoomUpdated(zoom_ * device_scale_);
2260   engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
2261   engine_->PluginSizeUpdated(available_area_.size());
2262   engine_->SetGrayscale(is_grayscale);
2263
2264   paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
2265 }
2266
2267 void PdfViewWebPlugin::HandleLoadPreviewPageMessage(
2268     const base::Value::Dict& message) {
2269   const std::string& url = *message.FindString("url");
2270   int dest_page_index = message.FindInt("index").value();
2271
2272   // For security reasons, crash if `url` is not for Print Preview.
2273   CHECK(IsPrintPreview());
2274   CHECK(IsPrintPreviewUrl(url));
2275
2276   DCHECK_GE(dest_page_index, 0);
2277   DCHECK_LT(dest_page_index, print_preview_page_count_);
2278
2279   // Print Preview JS will send the loadPreviewPage message for every page,
2280   // including the first page in the print preview, which has already been
2281   // loaded when handing the resetPrintPreviewMode message. Just ignore it.
2282   if (dest_page_index == 0)
2283     return;
2284
2285   int src_page_index = ExtractPrintPreviewPageIndex(url);
2286   DCHECK_GE(src_page_index, 0);
2287
2288   preview_pages_info_.push({.url = url, .dest_page_index = dest_page_index});
2289   LoadAvailablePreviewPage();
2290 }
2291
2292 void PdfViewWebPlugin::LoadAvailablePreviewPage() {
2293   if (preview_pages_info_.empty() ||
2294       document_load_state_ != DocumentLoadState::kComplete ||
2295       preview_document_load_state_ == DocumentLoadState::kLoading) {
2296     return;
2297   }
2298
2299   preview_document_load_state_ = DocumentLoadState::kLoading;
2300   const std::string& url = preview_pages_info_.front().url;
2301
2302   // Note that `last_progress_sent_` is not reset for preview page loads.
2303   LoadUrl(url, base::BindOnce(&PdfViewWebPlugin::DidOpenPreview,
2304                               weak_factory_.GetWeakPtr()));
2305 }
2306
2307 void PdfViewWebPlugin::DidOpenPreview(std::unique_ptr<UrlLoader> loader,
2308                                       int32_t result) {
2309   DCHECK_EQ(result, kSuccess);
2310   preview_client_ = std::make_unique<PreviewModeClient>(this);
2311   preview_engine_ = client_->CreateEngine(
2312       preview_client_.get(), PDFiumFormFiller::ScriptOption::kNoJavaScript);
2313   preview_engine_->PluginSizeUpdated({});
2314   preview_engine_->HandleDocumentLoad(std::move(loader), url_);
2315 }
2316
2317 void PdfViewWebPlugin::PreviewDocumentLoadComplete() {
2318   if (preview_document_load_state_ != DocumentLoadState::kLoading ||
2319       preview_pages_info_.empty()) {
2320     return;
2321   }
2322
2323   preview_document_load_state_ = DocumentLoadState::kComplete;
2324
2325   int dest_page_index = preview_pages_info_.front().dest_page_index;
2326   DCHECK_GT(dest_page_index, 0);
2327   preview_pages_info_.pop();
2328   DCHECK(preview_engine_);
2329   engine_->AppendPage(preview_engine_.get(), dest_page_index);
2330
2331   ++print_preview_loaded_page_count_;
2332   LoadNextPreviewPage();
2333 }
2334
2335 void PdfViewWebPlugin::PreviewDocumentLoadFailed() {
2336   client_->RecordComputedAction("PDF.PreviewDocumentLoadFailure");
2337   if (preview_document_load_state_ != DocumentLoadState::kLoading ||
2338       preview_pages_info_.empty()) {
2339     return;
2340   }
2341
2342   // Even if a print preview page failed to load, keep going.
2343   preview_document_load_state_ = DocumentLoadState::kFailed;
2344   preview_pages_info_.pop();
2345   ++print_preview_loaded_page_count_;
2346   LoadNextPreviewPage();
2347 }
2348
2349 void PdfViewWebPlugin::LoadNextPreviewPage() {
2350   if (!preview_pages_info_.empty()) {
2351     DCHECK_LT(print_preview_loaded_page_count_, print_preview_page_count_);
2352     LoadAvailablePreviewPage();
2353     return;
2354   }
2355
2356   if (print_preview_loaded_page_count_ == print_preview_page_count_)
2357     SendPrintPreviewLoadedNotification();
2358 }
2359
2360 void PdfViewWebPlugin::SendPrintPreviewLoadedNotification() {
2361   base::Value::Dict message;
2362   message.Set("type", "printPreviewLoaded");
2363   client_->PostMessage(std::move(message));
2364 }
2365
2366 void PdfViewWebPlugin::SendThumbnail(base::Value::Dict reply,
2367                                      Thumbnail thumbnail) {
2368   DCHECK_EQ(*reply.FindString("type"), "getThumbnailReply");
2369   DCHECK(reply.FindString("messageId"));
2370
2371   reply.Set("imageData", thumbnail.TakeData());
2372   reply.Set("width", thumbnail.image_size().width());
2373   reply.Set("height", thumbnail.image_size().height());
2374   client_->PostMessage(std::move(reply));
2375 }
2376
2377 gfx::Point PdfViewWebPlugin::FrameToPdfCoordinates(
2378     const gfx::PointF& frame_coordinates) const {
2379   // TODO(crbug.com/1288847): Use methods on `blink::WebPluginContainer`.
2380   return gfx::ToFlooredPoint(
2381              gfx::ScalePoint(frame_coordinates, device_scale_)) -
2382          gfx::Vector2d(available_area_.x(), 0);
2383 }
2384
2385 }  // namespace chrome_pdf