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.
5 #include "pdf/pdf_view_web_plugin.h"
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"
117 namespace chrome_pdf {
121 // The minimum zoom level allowed.
122 constexpr double kMinZoom = 0.01;
124 constexpr base::TimeDelta kFindResultCooldown = base::Milliseconds(100);
126 constexpr base::StringPiece kChromeExtensionHost =
127 "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/";
129 // Print Preview base URL.
130 constexpr base::StringPiece kChromePrintHost = "chrome://print/";
132 // Untrusted Print Preview base URL.
133 constexpr base::StringPiece kChromeUntrustedPrintHost =
134 "chrome-untrusted://print/";
136 // Same value as `printing::COMPLETE_PREVIEW_DOCUMENT_INDEX`.
137 constexpr int kCompletePDFIndex = -1;
139 // A different negative value to differentiate itself from `kCompletePDFIndex`.
140 constexpr int kInvalidPDFIndex = -2;
142 // Enumeration of pinch states.
143 // This should match PinchPhase enum in
144 // chrome/browser/resources/pdf/viewport.ts.
145 enum class PinchPhase {
153 // Initialization performed per renderer process. Initialization may be
154 // triggered from multiple plugin instances, but should only execute once.
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 {
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.
168 static PerProcessInitializer& GetInstance() {
169 static base::NoDestructor<PerProcessInitializer> instance;
174 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
176 DCHECK_GE(init_count_, 0);
177 if (init_count_++ > 0)
180 DCHECK(!IsSDKInitializedViaPlugin());
181 InitializeSDK(/*enable_v8=*/true, FontMappingMode::kBlink);
182 SetIsSDKInitializedViaPlugin(true);
186 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
188 DCHECK_GT(init_count_, 0);
189 if (--init_count_ > 0)
192 DCHECK(IsSDKInitializedViaPlugin());
194 SetIsSDKInitializedViaPlugin(false);
198 int init_count_ GUARDED_BY_CONTEXT(thread_checker_) = 0;
200 // TODO(crbug.com/1123731): Assuming PDFium is thread-hostile for now, and
201 // must use one thread exclusively.
202 THREAD_CHECKER(thread_checker_);
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());
214 bool IsPrintPreviewUrl(base::StringPiece url) {
215 return base::StartsWith(url, kChromeUntrustedPrintHost);
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;
227 if (url_substr[2] != "print.pdf")
228 return kInvalidPDFIndex;
231 if (!base::StringToInt(url_substr[1], &page_index))
232 return kInvalidPDFIndex;
236 bool IsPreviewingPDF(int print_preview_page_count) {
237 return print_preview_page_count == 0;
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");
248 base::Value::Dict reply;
249 reply.Set("type", reply_type);
250 reply.Set("messageId", *message.FindString("messageId"));
254 bool IsSaveDataSizeValid(size_t size) {
255 return size > 0 && size <= PdfViewWebPlugin::kMaximumSavedFileSize;
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);
266 std::unique_ptr<PdfAccessibilityDataHandler>
267 PdfViewWebPlugin::Client::CreateAccessibilityDataHandler(
268 PdfAccessibilityActionHandler* action_handler) {
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());
285 PdfViewWebPlugin::~PdfViewWebPlugin() = default;
287 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
288 const PDFiumEngine* PdfViewWebPlugin::engine() const {
289 return engine_.get();
292 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
293 PDFiumEngine* PdfViewWebPlugin::engine() {
294 return engine_.get();
297 bool PdfViewWebPlugin::Initialize(blink::WebPluginContainer* container) {
299 client_->SetPluginContainer(container);
301 DCHECK_EQ(container->Plugin(), this);
302 return InitializeCommon();
305 bool PdfViewWebPlugin::InitializeForTesting() {
306 return InitializeCommon();
309 bool PdfViewWebPlugin::InitializeCommon() {
310 // Allow the plugin to handle touch events.
311 client_->RequestTouchEventType(
312 blink::WebPluginContainer::kTouchEventRequestTypeRaw);
314 // Allow the plugin to handle find requests.
315 client_->UsePluginAsFindHandler();
317 absl::optional<ParsedParams> params = ParseWebPluginParams(initial_params_);
319 // The contents of `initial_params_` are no longer needed.
320 initial_params_ = {};
322 if (!params.has_value())
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).
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);
337 PerProcessInitializer::GetInstance().Acquire();
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.
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);
349 full_frame_ = params->full_frame;
350 background_color_ = params->background_color;
352 engine_ = client_->CreateEngine(this, params->script_option);
355 SendSetSmoothScrolling();
357 // Skip the remaining initialization when in Print Preview mode. Loading will
358 // continue after the plugin receives a "resetPrintPreviewMode" message.
359 if (IsPrintPreview())
362 last_progress_sent_ = 0;
363 LoadUrl(params->src_url, base::BindOnce(&PdfViewWebPlugin::DidOpen,
364 weak_factory_.GetWeakPtr()));
365 url_ = params->original_url;
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)
373 #endif // !BUILDFLAG(ENABLE_INK)
375 metrics_handler_ = std::make_unique<MetricsHandler>();
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));
387 void PdfViewWebPlugin::DidOpen(std::unique_ptr<UrlLoader> loader,
389 if (result == kSuccess) {
390 if (!engine_->HandleDocumentLoad(std::move(loader), url_)) {
391 document_load_state_ = DocumentLoadState::kLoading;
392 DocumentLoadFailed();
394 } else if (result != kErrorAborted) {
395 DocumentLoadFailed();
399 void PdfViewWebPlugin::Destroy() {
400 if (client_->PluginContainer()) {
401 // Explicitly destroy the PDFEngine during destruction as it may call back
403 preview_engine_.reset();
405 PerProcessInitializer::GetInstance().Release();
406 client_->SetPluginContainer(nullptr);
412 blink::WebPluginContainer* PdfViewWebPlugin::Container() const {
413 return client_->PluginContainer();
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
421 scriptable_receiver_.Reset(
422 isolate, PostMessageReceiver::Create(
423 isolate, client_->GetWeakPtr(), weak_factory_.GetWeakPtr(),
424 base::SequencedTaskRunnerHandle::Get()));
427 return scriptable_receiver_.Get(isolate);
430 bool PdfViewWebPlugin::SupportsKeyboardFocus() const {
431 return !IsPrintPreview();
434 void PdfViewWebPlugin::UpdateAllLifecyclePhases(
435 blink::DocumentUpdateReason reason) {}
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);
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);
456 // Layer translate is independent of scaling, so apply first.
457 if (!total_translate_.IsZero())
458 canvas->translate(total_translate_.x(), total_translate_.y());
460 if (device_to_css_scale_ != 1.0f)
461 canvas->scale(device_to_css_scale_, device_to_css_scale_);
463 // Position layer at plugin origin before layer scaling.
464 if (!plugin_rect_.origin().IsOrigin())
465 canvas->translate(plugin_rect_.x(), plugin_rect_.y());
467 if (snapshot_scale_ != 1.0f)
468 canvas->scale(snapshot_scale_, snapshot_scale_);
470 canvas->drawImage(snapshot_, 0, 0);
473 void PdfViewWebPlugin::UpdateGeometry(const gfx::Rect& window_rect,
474 const gfx::Rect& clip_rect,
475 const gfx::Rect& unobscured_rect,
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())
489 OnViewportChanged(window_rect, client_->DeviceScaleFactor());
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);
497 void PdfViewWebPlugin::UpdateScroll(const gfx::PointF& scroll_position) {
501 float max_x = std::max(document_size_.width() * static_cast<float>(zoom_) -
502 plugin_dip_size_.width(),
504 float max_y = std::max(document_size_.height() * static_cast<float>(zoom_) -
505 plugin_dip_size_.height(),
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_);
513 engine_->ScrolledToXPosition(scaled_scroll_position.x());
514 engine_->ScrolledToYPosition(scaled_scroll_position.y());
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();
524 has_focus_ = focused;
526 if (!has_focus_ || !SupportsKeyboardFocus())
529 if (focus_type != blink::mojom::FocusType::kBackward &&
530 focus_type != blink::mojom::FocusType::kForward) {
534 const int modifiers = focus_type == blink::mojom::FocusType::kForward
535 ? blink::WebInputEvent::kNoModifiers
536 : blink::WebInputEvent::kShiftKey;
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);
544 void PdfViewWebPlugin::UpdateVisibility(bool visibility) {}
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
553 std::unique_ptr<blink::WebInputEvent> scaled_event =
554 ui::ScaleWebInputEvent(event.Event(), viewport_to_dip_scale_);
556 const blink::WebInputEvent& event_to_handle =
557 scaled_event ? *scaled_event : event.Event();
559 const blink::WebInputEventResult result =
560 HandleWebInputEvent(event_to_handle)
561 ? blink::WebInputEventResult::kHandledApplication
562 : blink::WebInputEventResult::kNotHandled;
564 *cursor = cursor_type_;
569 void PdfViewWebPlugin::DidReceiveResponse(
570 const blink::WebURLResponse& response) {}
572 void PdfViewWebPlugin::DidReceiveData(const char* data, size_t data_length) {}
574 void PdfViewWebPlugin::DidFinishLoading() {}
576 void PdfViewWebPlugin::DidFailLoading(const blink::WebURLError& error) {}
578 bool PdfViewWebPlugin::SupportsPaginatedPrint() {
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();
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();
598 if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality))
601 print_params_ = print_params;
602 if (!engine_->HasPermission(DocumentPermission::kPrintHighQuality))
603 print_params_->rasterize_pdf = true;
605 engine_->PrintBegin();
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.
615 // Every `canvas` passed to this method should have a valid `metafile`.
616 printing::MetafileSkia* metafile = canvas->GetPrintingMetafile();
619 // `pages_to_print_` should be empty iff `printing_metafile_` is not set.
620 DCHECK_EQ(pages_to_print_.empty(), !printing_metafile_);
622 // The metafile should be the same across all calls for a given print job.
623 DCHECK(!printing_metafile_ || (printing_metafile_ == metafile));
625 if (!printing_metafile_)
626 printing_metafile_ = metafile;
628 pages_to_print_.push_back(page_number);
631 void PdfViewWebPlugin::PrintEnd() {
632 if (pages_to_print_.empty())
635 print_pages_called_ = true;
636 printing_metafile_->InitFromData(
637 engine_->PrintPages(pages_to_print_, print_params_.value()));
639 if (print_pages_called_)
640 client_->RecordComputedAction("PDF.PrintPage");
641 print_pages_called_ = false;
642 print_params_.reset();
645 printing_metafile_ = nullptr;
646 pages_to_print_.clear();
649 bool PdfViewWebPlugin::HasSelection() const {
650 return !selected_text_.IsEmpty();
653 blink::WebString PdfViewWebPlugin::SelectionAsText() const {
654 return selected_text_;
657 blink::WebString PdfViewWebPlugin::SelectionAsMarkup() const {
658 return selected_text_;
661 bool PdfViewWebPlugin::CanEditText() const {
662 return engine_->CanEditText();
665 bool PdfViewWebPlugin::HasEditableText() const {
666 return engine_->HasEditableText();
669 bool PdfViewWebPlugin::CanUndo() const {
670 return engine_->CanUndo();
673 bool PdfViewWebPlugin::CanRedo() const {
674 return engine_->CanRedo();
677 bool PdfViewWebPlugin::CanCopy() const {
678 return engine_->HasPermission(DocumentPermission::kCopy);
681 bool PdfViewWebPlugin::ExecuteEditCommand(const blink::WebString& name,
682 const blink::WebString& value) {
683 if (name == "SelectAll")
689 if (name == "Paste" || name == "PasteAndMatchStyle")
701 blink::WebURL PdfViewWebPlugin::LinkAtPosition(
702 const gfx::Point& /*position*/) const {
703 return GURL(link_under_cursor_);
706 bool PdfViewWebPlugin::StartFind(const blink::WebString& search_text,
709 ResetRecentlySentFindUpdate();
710 find_identifier_ = identifier;
711 engine_->StartFind(search_text.Utf8(), case_sensitive);
715 void PdfViewWebPlugin::SelectFindResult(bool forward, int identifier) {
716 find_identifier_ = identifier;
717 engine_->SelectFindResult(forward);
720 void PdfViewWebPlugin::StopFind() {
721 find_identifier_ = -1;
724 client_->ReportFindInPageTickmarks(tickmarks_);
727 bool PdfViewWebPlugin::CanRotateView() {
728 return !IsPrintPreview();
731 void PdfViewWebPlugin::RotateView(blink::WebPlugin::RotationType type) {
732 DCHECK(CanRotateView());
735 case blink::WebPlugin::RotationType::k90Clockwise:
736 engine_->RotateClockwise();
738 case blink::WebPlugin::RotationType::k90Counterclockwise:
739 engine_->RotateCounterclockwise();
744 bool PdfViewWebPlugin::ShouldDispatchImeEventsToPlugin() {
748 blink::WebTextInputType PdfViewWebPlugin::GetPluginTextInputType() {
749 return text_input_type_;
752 gfx::Rect PdfViewWebPlugin::GetPluginCaretBounds() {
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;
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);
773 void PdfViewWebPlugin::ImeFinishComposingTextForPlugin(
774 bool /*keep_selection*/) {
775 HandleImeCommit(composition_text_);
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));
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)
796 void PdfViewWebPlugin::Invalidate(const gfx::Rect& rect) {
798 deferred_invalidates_.push_back(rect);
802 gfx::Rect offset_rect = rect + available_area_.OffsetFromOrigin();
803 paint_manager_.InvalidateRect(offset_rect);
806 void PdfViewWebPlugin::DidScroll(const gfx::Vector2d& offset) {
807 if (!image_data_.drawsNothing())
808 paint_manager_.ScrollRect(available_area_, offset);
811 void PdfViewWebPlugin::ScrollToX(int x_screen_coords) {
812 const float x_scroll_pos = x_screen_coords / device_scale_;
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));
820 void PdfViewWebPlugin::ScrollToY(int y_screen_coords) {
821 const float y_scroll_pos = y_screen_coords / device_scale_;
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));
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_;
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));
840 void PdfViewWebPlugin::ScrollToPage(int page) {
841 if (!engine_ || engine_->GetNumberOfPages() == 0)
844 base::Value::Dict message;
845 message.Set("type", "goToPage");
846 message.Set("page", page);
847 client_->PostMessage(std::move(message));
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));
859 void PdfViewWebPlugin::NavigateToDestination(int page,
863 base::Value::Dict message;
864 message.Set("type", "navigateToDestination");
865 message.Set("page", page);
867 message.Set("x", static_cast<double>(*x));
869 message.Set("y", static_cast<double>(*y));
871 message.Set("zoom", static_cast<double>(*zoom));
872 client_->PostMessage(std::move(message));
875 void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) {
876 cursor_type_ = new_cursor_type;
879 void PdfViewWebPlugin::UpdateTickMarks(
880 const std::vector<gfx::Rect>& tickmarks) {
881 tickmarks_ = tickmarks;
884 void PdfViewWebPlugin::NotifyNumberOfFindResultsChanged(int total,
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)
892 // After stopping search and setting `find_identifier_` to -1 there still may
893 // be a NotifyNumberOfFindResultsChanged notification pending from engine.
895 if (find_identifier_ != -1) {
896 client_->ReportFindInPageMatchCount(find_identifier_, total, final_result);
899 client_->ReportFindInPageTickmarks(tickmarks_);
904 recently_sent_find_update_ = true;
905 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
907 base::BindOnce(&PdfViewWebPlugin::ResetRecentlySentFindUpdate,
908 weak_factory_.GetWeakPtr()),
909 kFindResultCooldown);
912 void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index,
914 if (find_identifier_ == -1 || !client_->PluginContainer())
917 DCHECK_GE(current_find_index, -1);
918 client_->ReportFindInPageSelection(find_identifier_, current_find_index + 1,
922 void PdfViewWebPlugin::NotifyTouchSelectionOccurred() {
923 base::Value::Dict message;
924 message.Set("type", "touchSelectionOccurred");
925 client_->PostMessage(std::move(message));
928 void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
929 caret_rect_ = gfx::ScaleToEnclosingRect(
930 caret_rect + available_area_.OffsetFromOrigin(), device_to_css_scale_);
933 void PdfViewWebPlugin::GetDocumentPassword(
934 base::OnceCallback<void(const std::string&)> callback) {
935 DCHECK(password_callback_.is_null());
936 password_callback_ = std::move(callback);
938 base::Value::Dict message;
939 message.Set("type", "getPassword");
940 client_->PostMessage(std::move(message));
943 void PdfViewWebPlugin::Beep() {
944 base::Value::Dict message;
945 message.Set("type", "beep");
946 client_->PostMessage(std::move(message));
949 void PdfViewWebPlugin::Alert(const std::string& message) {
950 client_->Alert(blink::WebString::FromUTF8(message));
953 bool PdfViewWebPlugin::Confirm(const std::string& message) {
954 return client_->Confirm(blink::WebString::FromUTF8(message));
957 std::string PdfViewWebPlugin::Prompt(const std::string& question,
958 const std::string& default_answer) {
960 ->Prompt(blink::WebString::FromUTF8(question),
961 blink::WebString::FromUTF8(default_answer))
965 std::string PdfViewWebPlugin::GetURL() {
969 void PdfViewWebPlugin::LoadUrl(base::StringPiece url,
970 LoadUrlCallback callback) {
972 request.url = std::string(url);
973 request.method = "GET";
974 request.ignore_redirects = true;
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)));
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));
997 void PdfViewWebPlugin::Print() {
1001 const bool can_print =
1002 engine_->HasPermission(DocumentPermission::kPrintLowQuality) ||
1003 engine_->HasPermission(DocumentPermission::kPrintHighQuality);
1007 base::ThreadTaskRunnerHandle::Get()->PostTask(
1008 FROM_HERE, base::BindOnce(&PdfViewWebPlugin::OnInvokePrintDialog,
1009 weak_factory_.GetWeakPtr()));
1012 void PdfViewWebPlugin::SubmitForm(const std::string& url,
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())
1022 request.url = resolved_url.spec();
1023 request.method = "POST";
1024 request.body.assign(static_cast<const char*>(data), length);
1026 form_loader_ = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
1027 form_loader_->Open(request, base::BindOnce(&PdfViewWebPlugin::DidFormOpen,
1028 weak_factory_.GetWeakPtr()));
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();
1037 std::unique_ptr<UrlLoader> PdfViewWebPlugin::CreateUrlLoader() {
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);
1047 return std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
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;
1059 while (searcher.NextMatchResult(match_index, match_length))
1060 results.push_back({.start_index = match_index, .length = match_length});
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);
1071 if (!full_frame_ || notified_browser_about_unsupported_feature_)
1074 notified_browser_about_unsupported_feature_ = true;
1075 pdf_service_->HasUnsupportedFeature();
1078 void PdfViewWebPlugin::DocumentLoadProgress(uint32_t available,
1079 uint32_t doc_size) {
1080 double progress = 0.0;
1082 progress = 100.0 * static_cast<double>(available) / doc_size;
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;
1089 std::min(std::log(static_cast<double>(available)) / kFactor, 100.0);
1092 // DocumentLoadComplete() will send the 100% load progress.
1093 if (progress >= 100)
1096 // Avoid sending too many progress messages over PostMessage.
1097 if (progress <= last_progress_sent_ + 1)
1100 SendLoadingProgress(progress);
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));
1109 text_input_type_ = type == PDFEngine::FocusFieldType::kText
1110 ? blink::WebTextInputType::kWebTextInputTypeText
1111 : blink::WebTextInputType::kWebTextInputTypeNone;
1112 client_->UpdateTextInputState();
1115 bool PdfViewWebPlugin::IsPrintPreview() const {
1116 return is_print_preview_;
1119 SkColor PdfViewWebPlugin::GetBackgroundColor() const {
1120 return background_color_;
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));
1130 void PdfViewWebPlugin::EnteredEditMode() {
1132 pdf_service_->SetPluginCanSave(true);
1134 base::Value::Dict message;
1135 message.Set("type", "setIsEditing");
1136 client_->PostMessage(std::move(message));
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));
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()));
1152 void PdfViewWebPlugin::SetLinkUnderCursor(
1153 const std::string& link_under_cursor) {
1154 link_under_cursor_ = link_under_cursor;
1157 bool PdfViewWebPlugin::IsValidLink(const std::string& url) {
1158 return base::Value(url).is_string();
1161 void PdfViewWebPlugin::SetCaretPosition(const gfx::PointF& position) {
1162 engine_->SetCaretPosition(FrameToPdfCoordinates(position));
1165 void PdfViewWebPlugin::MoveRangeSelectionExtent(const gfx::PointF& extent) {
1166 engine_->MoveRangeSelectionExtent(FrameToPdfCoordinates(extent));
1169 void PdfViewWebPlugin::SetSelectionBounds(const gfx::PointF& base,
1170 const gfx::PointF& extent) {
1171 engine_->SetSelectionBounds(FrameToPdfCoordinates(base),
1172 FrameToPdfCoordinates(extent));
1175 bool PdfViewWebPlugin::IsValid() const {
1176 return client_->HasFrame();
1179 blink::WebURL PdfViewWebPlugin::CompleteURL(
1180 const blink::WebString& partial_url) const {
1182 return client_->CompleteURL(partial_url);
1185 net::SiteForCookies PdfViewWebPlugin::SiteForCookies() const {
1187 return client_->SiteForCookies();
1190 void PdfViewWebPlugin::SetReferrerForRequest(
1191 blink::WebURLRequest& request,
1192 const blink::WebURL& referrer_url) {
1193 client_->SetReferrerForRequest(request, referrer_url);
1196 std::unique_ptr<blink::WebAssociatedURLLoader>
1197 PdfViewWebPlugin::CreateAssociatedURLLoader(
1198 const blink::WebAssociatedURLLoaderOptions& options) {
1199 return client_->CreateAssociatedURLLoader(options);
1202 void PdfViewWebPlugin::OnMessage(const base::Value::Dict& message) {
1203 using MessageHandler = void (PdfViewWebPlugin::*)(const base::Value::Dict&);
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},
1239 MessageHandler handler = kMessageHandlers.at(*message.FindString("type"));
1240 (this->*handler)(message);
1243 void PdfViewWebPlugin::HandleDisplayAnnotationsMessage(
1244 const base::Value::Dict& message) {
1245 engine_->DisplayAnnotations(message.FindBool("display").value());
1248 void PdfViewWebPlugin::HandleGetNamedDestinationMessage(
1249 const base::Value::Dict& message) {
1250 absl::optional<PDFEngine::NamedDestination> named_destination =
1251 engine_->GetNamedDestination(*message.FindString("namedDestination"));
1253 const int page_number = named_destination.has_value()
1254 ? base::checked_cast<int>(named_destination->page)
1257 base::Value::Dict reply =
1258 PrepareReplyMessage("getNamedDestinationReply", message);
1259 reply.Set("pageNumber", page_number);
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];
1268 view_stream << "," << named_destination->xyz_params;
1271 reply.Set("namedDestinationView", view_stream.str());
1274 client_->PostMessage(std::move(reply));
1277 void PdfViewWebPlugin::HandleGetPasswordCompleteMessage(
1278 const base::Value::Dict& message) {
1279 DCHECK(password_callback_);
1280 std::move(password_callback_).Run(*message.FindString("password"));
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);
1289 base::Value::Dict reply =
1290 PrepareReplyMessage("getSelectedTextReply", message);
1291 reply.Set("selectedText", selected_text);
1292 client_->PostMessage(std::move(reply));
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);
1300 engine_->RequestThumbnail(
1301 page_index, device_scale_,
1302 base::BindOnce(&PdfViewWebPlugin::SendThumbnail,
1303 weak_factory_.GetWeakPtr(), std::move(reply)));
1306 void PdfViewWebPlugin::HandlePrintMessage(
1307 const base::Value::Dict& /*message*/) {
1311 void PdfViewWebPlugin::HandleRotateClockwiseMessage(
1312 const base::Value::Dict& /*message*/) {
1313 engine_->RotateClockwise();
1316 void PdfViewWebPlugin::HandleRotateCounterclockwiseMessage(
1317 const base::Value::Dict& /*message*/) {
1318 engine_->RotateCounterclockwise();
1321 void PdfViewWebPlugin::HandleSaveAttachmentMessage(
1322 const base::Value::Dict& message) {
1323 const int index = message.FindInt("attachmentIndex").value();
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));
1332 std::vector<uint8_t> data = engine_->GetAttachmentData(index);
1333 base::Value data_to_save(
1334 IsSaveDataSizeValid(data.size()) ? data : std::vector<uint8_t>());
1336 base::Value::Dict reply = PrepareReplyMessage("saveAttachmentReply", message);
1337 reply.Set("dataToSave", std::move(data_to_save));
1338 client_->PostMessage(std::move(reply));
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));
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);
1356 #endif // BUILDFLAG(ENABLE_INK)
1358 case SaveRequestType::kOriginal:
1359 pdf_service_->SetPluginCanSave(false);
1361 pdf_service_->SetPluginCanSave(edit_mode_);
1363 case SaveRequestType::kEdited:
1364 SaveToBuffer(token);
1369 void PdfViewWebPlugin::HandleSelectAllMessage(
1370 const base::Value::Dict& /*message*/) {
1371 engine_->SelectAll();
1374 void PdfViewWebPlugin::HandleSetBackgroundColorMessage(
1375 const base::Value::Dict& message) {
1377 base::checked_cast<SkColor>(message.FindDouble("color").value());
1380 void PdfViewWebPlugin::HandleSetPresentationModeMessage(
1381 const base::Value::Dict& message) {
1382 engine_->SetReadOnly(message.FindBool("enablePresentationMode").value());
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);
1392 void PdfViewWebPlugin::HandleStopScrollingMessage(
1393 const base::Value::Dict& /*message*/) {
1394 stop_scrolling_ = true;
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);
1404 ui_direction_ = layout_options.direction();
1406 // TODO(crbug.com/1013800): Eliminate need to get document size from here.
1407 document_size_ = engine_->ApplyDocumentLayout(layout_options);
1409 OnGeometryChanged(zoom_, device_scale_);
1410 if (!document_size_.IsEmpty())
1411 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
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);
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"));
1426 received_viewport_message_ = true;
1427 stop_scrolling_ = false;
1428 const double zoom_ratio = new_zoom / zoom_;
1430 if (pinch_phase == PinchPhase::kStart) {
1431 scroll_offset_at_last_raster_ = scroll_offset;
1432 last_bitmap_smaller_ = false;
1433 needs_reraster_ = false;
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);
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);
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;
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(
1469 (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
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));
1489 paint_manager_.SetTransform(zoom_ratio, pinch_center,
1490 pinch_vector + paint_offset + scroll_delta,
1492 needs_reraster_ = false;
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;
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
1509 scroll_offset_at_last_raster_ = scroll_offset;
1512 // Bound the input parameters.
1513 new_zoom = std::max(kMinZoom, new_zoom);
1514 DCHECK(message.FindBool("userInitiated").has_value());
1516 double old_zoom = zoom_;
1519 OnGeometryChanged(old_zoom, device_scale_);
1520 if (!document_size_.IsEmpty())
1521 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1523 UpdateScroll(GetScrollPositionFromOffset(scroll_offset));
1526 void PdfViewWebPlugin::SaveToBuffer(const std::string& token) {
1527 engine_->KillFormFocus();
1529 base::Value::Dict message;
1530 message.Set("type", "saveData");
1531 message.Set("token", token);
1532 message.Set("fileName", GetFileNameForSaveFromUrl(url_));
1534 // Expose `edit_mode_` state for integration testing.
1535 message.Set("editModeForTesting", edit_mode_);
1537 base::Value data_to_save;
1539 base::Value::BlobStorage data = engine_->GetSaveData();
1540 if (IsSaveDataSizeValid(data.size()))
1541 data_to_save = base::Value(std::move(data));
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));
1552 #endif // BUILDFLAG(ENABLE_INK)
1555 message.Set("dataToSave", std::move(data_to_save));
1556 client_->PostMessage(std::move(message));
1559 void PdfViewWebPlugin::SaveToFile(const std::string& token) {
1560 engine_->KillFormFocus();
1562 base::Value::Dict message;
1563 message.Set("type", "consumeSaveToken");
1564 message.Set("token", token);
1565 client_->PostMessage(std::move(message));
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);
1572 void PdfViewWebPlugin::InvalidatePluginContainer() {
1573 client_->Invalidate();
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);
1583 gfx::PointF PdfViewWebPlugin::GetScrollPositionFromOffset(
1584 const gfx::Vector2dF& scroll_offset) const {
1585 gfx::PointF scroll_origin;
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(),
1596 return scroll_origin + scroll_offset;
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());
1607 PrepareForFirstPaint(ready);
1609 if (!received_viewport_message_ || !needs_reraster_)
1612 engine_->PrePaint();
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.
1619 gfx::IntersectRects(paint_rect, gfx::Rect(plugin_rect_.size()));
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);
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);
1635 for (gfx::Rect& pending_rect : pdf_pending) {
1636 pending_rect.Offset(available_area_.OffsetFromOrigin());
1637 pending.push_back(pending_rect);
1641 // Ensure the region above the first page (if any) is filled;
1642 const int32_t first_page_ypos = 0 == engine_->GetNumberOfPages()
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);
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);
1664 engine_->PostPaint();
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);
1672 InvalidateAfterPaintDone();
1675 void PdfViewWebPlugin::PrepareForFirstPaint(
1676 std::vector<PaintReadyRect>& ready) {
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);
1687 void PdfViewWebPlugin::OnGeometryChanged(double old_zoom,
1688 float old_device_scale) {
1689 RecalculateAreas(old_zoom, old_device_scale);
1691 if (accessibility_state_ == AccessibilityState::kLoaded)
1692 PrepareAndSetAccessibilityViewportInfo();
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_);
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);
1708 // The distance between top of the plugin and the bottom of the document in
1710 int bottom_of_document = GetDocumentPixelHeight();
1711 if (bottom_of_document < plugin_rect_.height())
1712 available_area_.set_height(bottom_of_document);
1714 CalculateBackgroundParts();
1716 engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
1717 engine_->PluginSizeUpdated(available_area_.size());
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());
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);
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);
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);
1746 int PdfViewWebPlugin::GetDocumentPixelWidth() const {
1747 return static_cast<int>(
1748 std::ceil(document_size_.width() * zoom_ * device_scale_));
1751 int PdfViewWebPlugin::GetDocumentPixelHeight() const {
1752 return static_cast<int>(
1753 std::ceil(document_size_.height() * zoom_ * device_scale_));
1756 void PdfViewWebPlugin::InvalidateAfterPaintDone() {
1757 if (deferred_invalidates_.empty())
1760 base::ThreadTaskRunnerHandle::Get()->PostTask(
1761 FROM_HERE, base::BindOnce(&PdfViewWebPlugin::ClearDeferredInvalidates,
1762 weak_factory_.GetWeakPtr()));
1765 void PdfViewWebPlugin::ClearDeferredInvalidates() {
1767 for (const gfx::Rect& rect : deferred_invalidates_)
1769 deferred_invalidates_.clear();
1772 void PdfViewWebPlugin::UpdateSnapshot(sk_sp<SkImage> snapshot) {
1774 cc::PaintImageBuilder::WithDefault()
1775 .set_image(std::move(snapshot), cc::PaintImage::GetNextContentId())
1776 .set_id(cc::PaintImage::GetNextId())
1778 if (!plugin_rect_.IsEmpty())
1779 InvalidatePluginContainer();
1782 void PdfViewWebPlugin::UpdateScaledValues() {
1783 total_translate_ = snapshot_translate_;
1785 if (viewport_to_dip_scale_ != 1.0f)
1786 total_translate_.Scale(1.0f / viewport_to_dip_scale_);
1789 void PdfViewWebPlugin::UpdateScale(float scale) {
1790 if (scale <= 0.0f) {
1795 viewport_to_dip_scale_ = scale;
1796 device_to_css_scale_ = 1.0f;
1797 UpdateScaledValues();
1800 void PdfViewWebPlugin::UpdateLayerTransform(float scale,
1801 const gfx::Vector2dF& translate) {
1802 snapshot_translate_ = translate;
1803 snapshot_scale_ = scale;
1804 UpdateScaledValues();
1807 void PdfViewWebPlugin::EnableAccessibility() {
1808 if (accessibility_state_ == AccessibilityState::kLoaded)
1811 if (accessibility_state_ == AccessibilityState::kOff)
1812 accessibility_state_ = AccessibilityState::kPending;
1814 if (document_load_state_ == DocumentLoadState::kComplete)
1815 LoadAccessibility();
1818 void PdfViewWebPlugin::HandleAccessibilityAction(
1819 const AccessibilityActionData& action_data) {
1820 engine_->HandleAccessibilityAction(action_data);
1823 base::WeakPtr<PdfViewPluginBase> PdfViewWebPlugin::GetWeakPtr() {
1824 return weak_factory_.GetWeakPtr();
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();
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();
1839 OnGeometryChanged(0, 0);
1840 if (!document_size_.IsEmpty())
1841 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1844 void PdfViewWebPlugin::OnDocumentLoadComplete() {
1845 RecordDocumentMetrics();
1851 if (accessibility_state_ == AccessibilityState::kPending)
1852 LoadAccessibility();
1855 void PdfViewWebPlugin::SendLoadingProgress(double percentage) {
1856 DCHECK(percentage == -1 || (percentage >= 0 && percentage <= 100));
1857 last_progress_sent_ = percentage;
1859 base::Value::Dict message;
1860 message.Set("type", "loadProgress");
1861 message.Set("progress", percentage);
1862 client_->PostMessage(std::move(message));
1865 void PdfViewWebPlugin::SetAccessibilityDocInfo(AccessibilityDocInfo doc_info) {
1866 pdf_accessibility_data_handler_->SetAccessibilityDocInfo(std::move(doc_info));
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));
1879 void PdfViewWebPlugin::SetAccessibilityViewportInfo(
1880 AccessibilityViewportInfo viewport_info) {
1881 pdf_accessibility_data_handler_->SetAccessibilityViewportInfo(
1882 std::move(viewport_info));
1885 void PdfViewWebPlugin::SetContentRestrictions(int content_restrictions) {
1886 pdf_service_->UpdateContentRestrictions(content_restrictions);
1889 void PdfViewWebPlugin::DidStartLoading() {
1890 if (did_call_start_loading_)
1893 client_->DidStartLoading();
1894 did_call_start_loading_ = true;
1897 void PdfViewWebPlugin::DidStopLoading() {
1898 if (!did_call_start_loading_)
1901 client_->DidStopLoading();
1902 did_call_start_loading_ = false;
1905 void PdfViewWebPlugin::NotifySelectionChanged(const gfx::PointF& left,
1907 const gfx::PointF& right,
1909 pdf_service_->SelectionChanged(left, left_height, right, right_height);
1912 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1913 void PdfViewWebPlugin::UserMetricsRecordAction(const std::string& action) {
1914 client_->RecordComputedAction(action);
1917 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1918 PaintManager& PdfViewWebPlugin::paint_manager() {
1919 return paint_manager_;
1922 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1923 const gfx::Rect& PdfViewWebPlugin::available_area() const {
1924 return available_area_;
1927 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1928 double PdfViewWebPlugin::zoom() const {
1932 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1933 bool PdfViewWebPlugin::full_frame() const {
1937 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1938 const gfx::Rect& PdfViewWebPlugin::plugin_rect() const {
1939 return plugin_rect_;
1942 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1943 float PdfViewWebPlugin::device_scale() const {
1944 return device_scale_;
1947 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1948 PdfViewPluginBase::DocumentLoadState PdfViewWebPlugin::document_load_state()
1950 return document_load_state_;
1953 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1954 void PdfViewWebPlugin::set_document_load_state(DocumentLoadState state) {
1955 document_load_state_ = state;
1958 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1959 PdfViewPluginBase::AccessibilityState PdfViewWebPlugin::accessibility_state()
1961 return accessibility_state_;
1964 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1965 void PdfViewWebPlugin::set_accessibility_state(AccessibilityState state) {
1966 accessibility_state_ = state;
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_;
1974 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1975 void PdfViewWebPlugin::increment_next_accessibility_page_index() {
1976 ++next_accessibility_page_index_;
1979 // TODO(crbug.com/1302059): Delete after merging with `PdfViewPluginBase`.
1980 void PdfViewWebPlugin::reset_next_accessibility_page_index() {
1981 next_accessibility_page_index_ = 0;
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);
1989 css_plugin_rect_ = new_plugin_rect_in_css_pixel;
1991 if (new_device_scale == device_scale_ &&
1992 new_plugin_rect_in_css_pixel == plugin_rect_) {
1996 const float old_device_scale = device_scale_;
1997 device_scale_ = new_device_scale;
1998 plugin_rect_ = new_plugin_rect_in_css_pixel;
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)
2006 paint_manager_.SetSize(plugin_rect_.size(), device_scale_);
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;
2018 // Skip updating the geometry if the new image data buffer is empty.
2019 if (image_data_.drawsNothing())
2022 OnGeometryChanged(zoom_, old_device_scale);
2025 bool PdfViewWebPlugin::SelectAll() {
2029 engine_->SelectAll();
2033 bool PdfViewWebPlugin::Cut() {
2034 if (!HasSelection() || !CanEditText())
2037 engine_->ReplaceSelection("");
2041 bool PdfViewWebPlugin::Paste(const blink::WebString& value) {
2045 engine_->ReplaceSelection(value.Utf8());
2049 bool PdfViewWebPlugin::Undo() {
2057 bool PdfViewWebPlugin::Redo() {
2065 bool PdfViewWebPlugin::HandleWebInputEvent(const blink::WebInputEvent& event) {
2066 // Ignore user input in read-only mode.
2067 if (engine_->IsReadOnly())
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),
2076 const blink::WebInputEvent& event_to_handle =
2077 transformed_event ? *transformed_event : event;
2079 if (engine_->HandleInputEvent(event_to_handle))
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) {
2089 // Return true for unhandled clicks so the plugin takes focus.
2090 return event_to_handle.GetType() == blink::WebInputEvent::Type::kMouseDown;
2093 void PdfViewWebPlugin::HandleImeCommit(const blink::WebString& text) {
2097 std::u16string text16 = text.Utf16();
2098 composition_text_.Reset();
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];
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];
2113 blink::WebCoalescedInputEvent input_event(char_event, ui::LatencyInfo());
2114 ui::Cursor dummy_cursor_info;
2115 HandleInputEvent(input_event, &dummy_cursor_info);
2119 void PdfViewWebPlugin::OnInvokePrintDialog() {
2123 void PdfViewWebPlugin::ResetRecentlySentFindUpdate() {
2124 recently_sent_find_update_ = false;
2127 void PdfViewWebPlugin::RecordDocumentMetrics() {
2128 if (!metrics_handler_)
2131 metrics_handler_->RecordDocumentMetrics(engine_->GetDocumentMetadata());
2132 metrics_handler_->RecordAttachmentTypes(
2133 engine_->GetDocumentAttachmentInfoList());
2136 void PdfViewWebPlugin::SendAttachments() {
2137 const std::vector<DocumentAttachmentInfo>& attachment_infos =
2138 engine_->GetDocumentAttachmentInfoList();
2139 if (attachment_infos.empty())
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
2146 const int size = attachment_info.size_bytes <= kMaximumSavedFileSize
2147 ? static_cast<int>(attachment_info.size_bytes)
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));
2157 base::Value::Dict message;
2158 message.Set("type", "attachments");
2159 message.Set("attachmentsData", std::move(attachments));
2160 client_->PostMessage(std::move(message));
2163 void PdfViewWebPlugin::SendBookmarks() {
2164 base::Value::List bookmarks = engine_->GetBookmarks();
2165 if (bookmarks.empty())
2168 base::Value::Dict message;
2169 message.Set("type", "bookmarks");
2170 message.Set("bookmarksData", std::move(bookmarks));
2171 client_->PostMessage(std::move(message));
2174 void PdfViewWebPlugin::SendMetadata() {
2175 base::Value::Dict metadata;
2176 const DocumentMetadata& document_metadata = engine_->GetDocumentMetadata();
2178 const std::string version = FormatPdfVersion(document_metadata.version);
2179 if (!version.empty())
2180 metadata.Set("version", version);
2182 metadata.Set("fileSize", ui::FormatBytes(document_metadata.size_bytes));
2184 metadata.Set("linearized", document_metadata.linearized);
2186 if (!document_metadata.title.empty())
2187 metadata.Set("title", document_metadata.title);
2189 if (!document_metadata.author.empty())
2190 metadata.Set("author", document_metadata.author);
2192 if (!document_metadata.subject.empty())
2193 metadata.Set("subject", document_metadata.subject);
2195 if (!document_metadata.keywords.empty())
2196 metadata.Set("keywords", document_metadata.keywords);
2198 if (!document_metadata.creator.empty())
2199 metadata.Set("creator", document_metadata.creator);
2201 if (!document_metadata.producer.empty())
2202 metadata.Set("producer", document_metadata.producer);
2204 if (!document_metadata.creation_date.is_null()) {
2205 metadata.Set("creationDate", base::TimeFormatShortDateAndTime(
2206 document_metadata.creation_date));
2209 if (!document_metadata.mod_date.is_null()) {
2210 metadata.Set("modDate",
2211 base::TimeFormatShortDateAndTime(document_metadata.mod_date));
2214 metadata.Set("pageSize", FormatPageSize(engine_->GetUniformPageSizePoints()));
2216 metadata.Set("canSerializeDocument",
2217 IsSaveDataSizeValid(engine_->GetLoadedByteSize()));
2219 base::Value::Dict message;
2220 message.Set("type", "metadata");
2221 message.Set("metadataData", std::move(metadata));
2222 client_->PostMessage(std::move(message));
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();
2231 // For security reasons, crash if `url` is not for Print Preview.
2232 CHECK(IsPrintPreview());
2233 CHECK(IsPrintPreviewUrl(url));
2235 DCHECK_GE(print_preview_page_count, 0);
2237 int page_index = ExtractPrintPreviewPageIndex(url);
2238 if (IsPreviewingPDF(print_preview_page_count)) {
2239 DCHECK_EQ(page_index, kCompletePDFIndex);
2241 DCHECK_GE(page_index, 0);
2244 print_preview_page_count_ = print_preview_page_count;
2245 print_preview_loaded_page_count_ = 0;
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();
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);
2264 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
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();
2272 // For security reasons, crash if `url` is not for Print Preview.
2273 CHECK(IsPrintPreview());
2274 CHECK(IsPrintPreviewUrl(url));
2276 DCHECK_GE(dest_page_index, 0);
2277 DCHECK_LT(dest_page_index, print_preview_page_count_);
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)
2285 int src_page_index = ExtractPrintPreviewPageIndex(url);
2286 DCHECK_GE(src_page_index, 0);
2288 preview_pages_info_.push({.url = url, .dest_page_index = dest_page_index});
2289 LoadAvailablePreviewPage();
2292 void PdfViewWebPlugin::LoadAvailablePreviewPage() {
2293 if (preview_pages_info_.empty() ||
2294 document_load_state_ != DocumentLoadState::kComplete ||
2295 preview_document_load_state_ == DocumentLoadState::kLoading) {
2299 preview_document_load_state_ = DocumentLoadState::kLoading;
2300 const std::string& url = preview_pages_info_.front().url;
2302 // Note that `last_progress_sent_` is not reset for preview page loads.
2303 LoadUrl(url, base::BindOnce(&PdfViewWebPlugin::DidOpenPreview,
2304 weak_factory_.GetWeakPtr()));
2307 void PdfViewWebPlugin::DidOpenPreview(std::unique_ptr<UrlLoader> loader,
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_);
2317 void PdfViewWebPlugin::PreviewDocumentLoadComplete() {
2318 if (preview_document_load_state_ != DocumentLoadState::kLoading ||
2319 preview_pages_info_.empty()) {
2323 preview_document_load_state_ = DocumentLoadState::kComplete;
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);
2331 ++print_preview_loaded_page_count_;
2332 LoadNextPreviewPage();
2335 void PdfViewWebPlugin::PreviewDocumentLoadFailed() {
2336 client_->RecordComputedAction("PDF.PreviewDocumentLoadFailure");
2337 if (preview_document_load_state_ != DocumentLoadState::kLoading ||
2338 preview_pages_info_.empty()) {
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();
2349 void PdfViewWebPlugin::LoadNextPreviewPage() {
2350 if (!preview_pages_info_.empty()) {
2351 DCHECK_LT(print_preview_loaded_page_count_, print_preview_page_count_);
2352 LoadAvailablePreviewPage();
2356 if (print_preview_loaded_page_count_ == print_preview_page_count_)
2357 SendPrintPreviewLoadedNotification();
2360 void PdfViewWebPlugin::SendPrintPreviewLoadedNotification() {
2361 base::Value::Dict message;
2362 message.Set("type", "printPreviewLoaded");
2363 client_->PostMessage(std::move(message));
2366 void PdfViewWebPlugin::SendThumbnail(base::Value::Dict reply,
2367 Thumbnail thumbnail) {
2368 DCHECK_EQ(*reply.FindString("type"), "getThumbnailReply");
2369 DCHECK(reply.FindString("messageId"));
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));
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);
2385 } // namespace chrome_pdf