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/check_op.h"
18 #include "base/containers/fixed_flat_map.h"
19 #include "base/containers/queue.h"
20 #include "base/debug/crash_logging.h"
21 #include "base/feature_list.h"
22 #include "base/functional/bind.h"
23 #include "base/functional/callback.h"
24 #include "base/i18n/char_iterator.h"
25 #include "base/i18n/rtl.h"
26 #include "base/i18n/string_search.h"
27 #include "base/i18n/time_formatting.h"
28 #include "base/logging.h"
29 #include "base/memory/raw_ptr.h"
30 #include "base/no_destructor.h"
31 #include "base/notreached.h"
32 #include "base/numerics/safe_conversions.h"
33 #include "base/strings/escape.h"
34 #include "base/strings/strcat.h"
35 #include "base/strings/string_number_conversions.h"
36 #include "base/strings/string_piece.h"
37 #include "base/strings/string_split.h"
38 #include "base/strings/string_util.h"
39 #include "base/task/sequenced_task_runner.h"
40 #include "base/task/single_thread_task_runner.h"
41 #include "base/thread_annotations.h"
42 #include "base/threading/thread_checker.h"
43 #include "base/time/time.h"
44 #include "base/values.h"
45 #include "cc/paint/paint_canvas.h"
46 #include "cc/paint/paint_flags.h"
47 #include "cc/paint/paint_image.h"
48 #include "cc/paint/paint_image_builder.h"
49 #include "net/cookies/site_for_cookies.h"
50 #include "pdf/accessibility.h"
51 #include "pdf/accessibility_structs.h"
52 #include "pdf/buildflags.h"
53 #include "pdf/content_restriction.h"
54 #include "pdf/document_layout.h"
55 #include "pdf/loader/result_codes.h"
56 #include "pdf/loader/url_loader.h"
57 #include "pdf/metrics_handler.h"
58 #include "pdf/mojom/pdf.mojom.h"
59 #include "pdf/paint_manager.h"
60 #include "pdf/paint_ready_rect.h"
61 #include "pdf/parsed_params.h"
62 #include "pdf/pdf_accessibility_data_handler.h"
63 #include "pdf/pdf_engine.h"
64 #include "pdf/pdf_features.h"
65 #include "pdf/pdf_init.h"
66 #include "pdf/pdfium/pdfium_engine.h"
67 #include "pdf/post_message_receiver.h"
68 #include "pdf/ui/document_properties.h"
69 #include "pdf/ui/file_name.h"
70 #include "pdf/ui/thumbnail.h"
71 #include "printing/metafile_skia.h"
72 #include "printing/units.h"
73 #include "services/network/public/mojom/referrer_policy.mojom-shared.h"
74 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
75 #include "third_party/blink/public/common/input/web_coalesced_input_event.h"
76 #include "third_party/blink/public/common/input/web_input_event.h"
77 #include "third_party/blink/public/common/input/web_keyboard_event.h"
78 #include "third_party/blink/public/common/metrics/document_update_reason.h"
79 #include "third_party/blink/public/mojom/input/focus_type.mojom-shared.h"
80 #include "third_party/blink/public/platform/platform.h"
81 #include "third_party/blink/public/platform/web_input_event_result.h"
82 #include "third_party/blink/public/platform/web_string.h"
83 #include "third_party/blink/public/platform/web_text_input_type.h"
84 #include "third_party/blink/public/platform/web_url.h"
85 #include "third_party/blink/public/platform/web_url_error.h"
86 #include "third_party/blink/public/platform/web_url_response.h"
87 #include "third_party/blink/public/web/web_associated_url_loader.h"
88 #include "third_party/blink/public/web/web_associated_url_loader_options.h"
89 #include "third_party/blink/public/web/web_document.h"
90 #include "third_party/blink/public/web/web_frame.h"
91 #include "third_party/blink/public/web/web_plugin_container.h"
92 #include "third_party/blink/public/web/web_plugin_params.h"
93 #include "third_party/blink/public/web/web_print_params.h"
94 #include "third_party/blink/public/web/web_print_preset_options.h"
95 #include "third_party/blink/public/web/web_view.h"
96 #include "third_party/blink/public/web/web_widget.h"
97 #include "third_party/skia/include/core/SkBitmap.h"
98 #include "third_party/skia/include/core/SkColor.h"
99 #include "third_party/skia/include/core/SkImage.h"
100 #include "third_party/skia/include/core/SkImageInfo.h"
101 #include "third_party/skia/include/core/SkRect.h"
102 #include "third_party/skia/include/core/SkRefCnt.h"
103 #include "third_party/skia/include/core/SkSize.h"
104 #include "ui/base/cursor/cursor.h"
105 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
106 #include "ui/base/text/bytes_formatting.h"
107 #include "ui/events/base_event_utils.h"
108 #include "ui/events/blink/blink_event_util.h"
109 #include "ui/events/keycodes/keyboard_codes.h"
110 #include "ui/gfx/geometry/point.h"
111 #include "ui/gfx/geometry/point_conversions.h"
112 #include "ui/gfx/geometry/point_f.h"
113 #include "ui/gfx/geometry/rect.h"
114 #include "ui/gfx/geometry/size.h"
115 #include "ui/gfx/geometry/skia_conversions.h"
116 #include "ui/gfx/geometry/vector2d.h"
117 #include "ui/gfx/geometry/vector2d_f.h"
118 #include "ui/gfx/range/range.h"
119 #include "url/gurl.h"
120 #include "v8/include/v8.h"
122 namespace chrome_pdf {
126 // The minimum zoom level allowed.
127 constexpr double kMinZoom = 0.01;
129 // A delay to wait between each accessibility page to keep the system
131 constexpr base::TimeDelta kAccessibilityPageDelay = base::Milliseconds(100);
133 constexpr base::TimeDelta kFindResultCooldown = base::Milliseconds(100);
135 constexpr base::StringPiece kChromeExtensionHost =
136 "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/";
138 // Print Preview base URL.
139 constexpr base::StringPiece kChromePrintHost = "chrome://print/";
141 // Untrusted Print Preview base URL.
142 constexpr base::StringPiece kChromeUntrustedPrintHost =
143 "chrome-untrusted://print/";
145 // Same value as `printing::COMPLETE_PREVIEW_DOCUMENT_INDEX`.
146 constexpr int kCompletePDFIndex = -1;
148 // A different negative value to differentiate itself from `kCompletePDFIndex`.
149 constexpr int kInvalidPDFIndex = -2;
151 // Enumeration of pinch states.
152 // This should match PinchPhase enum in
153 // chrome/browser/resources/pdf/viewport.ts.
154 enum class PinchPhase {
162 // Initialization performed per renderer process. Initialization may be
163 // triggered from multiple plugin instances, but should only execute once.
165 // TODO(crbug.com/1123621): We may be able to simplify this once we've figured
166 // out exactly which processes need to initialize and shutdown PDFium.
167 class PerProcessInitializer final {
169 ~PerProcessInitializer() {
170 // On some configs, thread checker is trivially destructible, which makes
171 // `PerProcessInitializer` trivially destructible as well. This is a problem
172 // because `base::NoDestructor` only allows non-trivially destructible
173 // types. Force `PerProcessInitializer` to be non-trivially destructible by
174 // declaring a non-default destructor.
177 static PerProcessInitializer& GetInstance() {
178 static base::NoDestructor<PerProcessInitializer> instance;
182 void Acquire(bool use_skia) {
183 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
185 DCHECK_GE(init_count_, 0);
186 if (init_count_++ > 0)
189 DCHECK(!IsSDKInitializedViaPlugin());
190 InitializeSDK(/*enable_v8=*/true, use_skia, FontMappingMode::kBlink);
191 SetIsSDKInitializedViaPlugin(true);
195 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
197 DCHECK_GT(init_count_, 0);
198 if (--init_count_ > 0)
201 DCHECK(IsSDKInitializedViaPlugin());
203 SetIsSDKInitializedViaPlugin(false);
207 int init_count_ GUARDED_BY_CONTEXT(thread_checker_) = 0;
209 // TODO(crbug.com/1123731): Assuming PDFium is thread-hostile for now, and
210 // must use one thread exclusively.
211 THREAD_CHECKER(thread_checker_);
214 base::Value::Dict DictFromRect(const gfx::Rect& rect) {
215 base::Value::Dict dict;
216 dict.Set("x", rect.x());
217 dict.Set("y", rect.y());
218 dict.Set("width", rect.width());
219 dict.Set("height", rect.height());
223 bool IsPrintPreviewUrl(base::StringPiece url) {
224 return base::StartsWith(url, kChromeUntrustedPrintHost);
227 int ExtractPrintPreviewPageIndex(base::StringPiece src_url) {
228 // Sample `src_url` format: chrome-untrusted://print/id/page_index/print.pdf
229 // The page_index is zero-based, but can be negative with special meanings.
230 std::vector<base::StringPiece> url_substr =
231 base::SplitStringPiece(src_url.substr(kChromeUntrustedPrintHost.size()),
232 "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
233 if (url_substr.size() != 3)
234 return kInvalidPDFIndex;
236 if (url_substr[2] != "print.pdf")
237 return kInvalidPDFIndex;
240 if (!base::StringToInt(url_substr[1], &page_index))
241 return kInvalidPDFIndex;
245 bool IsPreviewingPDF(int print_preview_page_count) {
246 return print_preview_page_count == 0;
249 // Prepares messages from the plugin that reply to messages from the embedder.
250 // If the "type" value of `message` is "foo", then the `reply_type` must be
251 // "fooReply". The `message` from the embedder must have a "messageId" value
252 // that will be copied to the reply message.
253 base::Value::Dict PrepareReplyMessage(base::StringPiece reply_type,
254 const base::Value::Dict& message) {
255 DCHECK_EQ(reply_type, *message.FindString("type") + "Reply");
257 base::Value::Dict reply;
258 reply.Set("type", reply_type);
259 reply.Set("messageId", *message.FindString("messageId"));
263 bool IsSaveDataSizeValid(size_t size) {
264 return size > 0 && size <= PdfViewWebPlugin::kMaximumSavedFileSize;
269 std::unique_ptr<PDFiumEngine> PdfViewWebPlugin::Client::CreateEngine(
270 PDFEngine::Client* client,
271 PDFiumFormFiller::ScriptOption script_option) {
272 return std::make_unique<PDFiumEngine>(client, script_option);
275 std::unique_ptr<PdfAccessibilityDataHandler>
276 PdfViewWebPlugin::Client::CreateAccessibilityDataHandler(
277 PdfAccessibilityActionHandler* action_handler,
278 PdfAccessibilityImageFetcher* image_fetcher) {
282 PdfViewWebPlugin::PdfViewWebPlugin(
283 std::unique_ptr<Client> client,
284 mojo::AssociatedRemote<pdf::mojom::PdfService> pdf_service,
285 const blink::WebPluginParams& params)
286 : client_(std::move(client)),
287 pdf_service_(std::move(pdf_service)),
288 initial_params_(params),
289 pdf_accessibility_data_handler_(
290 client_->CreateAccessibilityDataHandler(this, this)) {
291 DCHECK(pdf_service_);
292 pdf_service_->SetListener(listener_receiver_.BindNewPipeAndPassRemote());
295 PdfViewWebPlugin::~PdfViewWebPlugin() = default;
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(params->use_skia);
340 // Check if the PDF is being loaded in the PDF chrome extension. We only allow
341 // the plugin to be loaded in the extension and print preview to avoid
342 // exposing sensitive APIs directly to external websites.
344 // This is enforced before creating the plugin (see
345 // `pdf::CreateInternalPlugin()`), so we just `CHECK` for defense-in-depth.
346 const std::string& embedder_origin = client_->GetEmbedderOriginString();
347 is_print_preview_ = (embedder_origin == kChromePrintHost);
348 CHECK(IsPrintPreview() || embedder_origin == kChromeExtensionHost);
350 full_frame_ = params->full_frame;
351 background_color_ = params->background_color;
353 engine_ = client_->CreateEngine(this, params->script_option);
356 SendSetSmoothScrolling();
358 // Skip the remaining initialization when in Print Preview mode. Loading will
359 // continue after the plugin receives a "resetPrintPreviewMode" message.
360 if (IsPrintPreview())
363 last_progress_sent_ = 0;
364 LoadUrl(params->src_url, base::BindOnce(&PdfViewWebPlugin::DidOpen,
365 weak_factory_.GetWeakPtr()));
366 url_ = params->original_url;
368 // Not all edits go through the PDF plugin's form filler. The plugin instance
369 // can be restarted by exiting annotation mode on ChromeOS, which can set the
370 // document to an edited state.
371 edit_mode_ = params->has_edits;
372 #if !BUILDFLAG(ENABLE_INK)
374 #endif // !BUILDFLAG(ENABLE_INK)
376 metrics_handler_ = std::make_unique<MetricsHandler>();
380 void PdfViewWebPlugin::SendSetSmoothScrolling() {
381 base::Value::Dict message;
382 message.Set("type", "setSmoothScrolling");
383 message.Set("smoothScrolling",
384 blink::Platform::Current()->IsScrollAnimatorEnabled());
385 client_->PostMessage(std::move(message));
388 void PdfViewWebPlugin::DidOpen(std::unique_ptr<UrlLoader> loader,
390 if (result == kSuccess) {
391 if (!engine_->HandleDocumentLoad(std::move(loader), url_)) {
392 document_load_state_ = DocumentLoadState::kLoading;
393 DocumentLoadFailed();
395 } else if (result != kErrorAborted) {
396 DocumentLoadFailed();
400 void PdfViewWebPlugin::Destroy() {
402 // Explicitly destroy the PDFEngine during destruction as it may call back
404 preview_engine_.reset();
406 PerProcessInitializer::GetInstance().Release();
409 client_->SetPluginContainer(nullptr);
414 blink::WebPluginContainer* PdfViewWebPlugin::Container() const {
415 return client_->PluginContainer();
418 v8::Local<v8::Object> PdfViewWebPlugin::V8ScriptableObject(
419 v8::Isolate* isolate) {
420 if (scriptable_receiver_.IsEmpty()) {
421 // TODO(crbug.com/1123731): Messages should not be handled on the renderer
423 scriptable_receiver_.Reset(
424 isolate, PostMessageReceiver::Create(
425 isolate, client_->GetWeakPtr(), weak_factory_.GetWeakPtr(),
426 base::SequencedTaskRunner::GetCurrentDefault()));
429 return scriptable_receiver_.Get(isolate);
432 bool PdfViewWebPlugin::SupportsKeyboardFocus() const {
433 return !IsPrintPreview();
436 void PdfViewWebPlugin::UpdateAllLifecyclePhases(
437 blink::DocumentUpdateReason reason) {}
439 void PdfViewWebPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) {
440 // Clip the intersection of the paint rect and the plugin rect, so that
441 // painting outside the plugin or the paint rect area can be avoided.
442 // Note: `rect` is in CSS pixels. We need to use `css_plugin_rect_`
443 // to calculate the intersection.
444 SkRect invalidate_rect =
445 gfx::RectToSkRect(gfx::IntersectRects(css_plugin_rect_, rect));
446 cc::PaintCanvasAutoRestore auto_restore(canvas, /*save=*/true);
447 canvas->clipRect(invalidate_rect);
449 // Paint with the plugin's background color if the snapshot is not ready.
450 if (snapshot_.GetSkImageInfo().isEmpty()) {
451 cc::PaintFlags flags;
452 flags.setBlendMode(SkBlendMode::kSrc);
453 flags.setColor(GetBackgroundColor());
454 canvas->drawRect(invalidate_rect, flags);
458 // Layer translate is independent of scaling, so apply first.
459 if (!total_translate_.IsZero())
460 canvas->translate(total_translate_.x(), total_translate_.y());
462 // Position layer at plugin origin before layer scaling.
463 if (!plugin_rect_.origin().IsOrigin())
464 canvas->translate(plugin_rect_.x(), plugin_rect_.y());
466 if (snapshot_scale_ != 1.0f)
467 canvas->scale(snapshot_scale_, snapshot_scale_);
469 canvas->drawImage(snapshot_, 0, 0);
472 void PdfViewWebPlugin::UpdateGeometry(const gfx::Rect& window_rect,
473 const gfx::Rect& clip_rect,
474 const gfx::Rect& unobscured_rect,
476 // An empty `window_rect` can be received here in the following cases:
477 // - If the embedded plugin size is 0.
478 // - If the embedded plugin size is not 0, it can come from re-layouts during
479 // the plugin initialization.
480 // For either case, there is no need to create a graphic device to display
481 // a PDF in an empty window. Since an empty `window_rect` can cause failure
482 // to create the graphic device, avoid all updates on the geometries and the
483 // device scales used by the plugin, the PaintManager and the PDFiumEngine
484 // unless a non-empty `window_rect` is received.
485 if (window_rect.IsEmpty())
488 OnViewportChanged(window_rect, client_->DeviceScaleFactor());
490 gfx::PointF scroll_position = client_->GetScrollPosition();
491 // Convert back to CSS pixels.
492 scroll_position.Scale(1.0f / device_scale_);
493 UpdateScroll(scroll_position);
496 void PdfViewWebPlugin::UpdateScroll(const gfx::PointF& scroll_position) {
500 float max_x = std::max(document_size_.width() * static_cast<float>(zoom_) -
501 plugin_dip_size_.width(),
503 float max_y = std::max(document_size_.height() * static_cast<float>(zoom_) -
504 plugin_dip_size_.height(),
507 gfx::PointF scaled_scroll_position(
508 std::clamp(scroll_position.x(), 0.0f, max_x),
509 std::clamp(scroll_position.y(), 0.0f, max_y));
510 scaled_scroll_position.Scale(device_scale_);
512 engine_->ScrolledToXPosition(scaled_scroll_position.x());
513 engine_->ScrolledToYPosition(scaled_scroll_position.y());
516 void PdfViewWebPlugin::UpdateFocus(bool focused,
517 blink::mojom::FocusType focus_type) {
518 if (has_focus_ != focused) {
519 engine_->UpdateFocus(focused);
520 client_->UpdateTextInputState();
522 // Make sure `this` is still alive after the UpdateSelectionBounds() call.
523 auto weak_this = weak_factory_.GetWeakPtr();
524 client_->UpdateSelectionBounds();
529 has_focus_ = focused;
531 if (!has_focus_ || !SupportsKeyboardFocus())
534 if (focus_type != blink::mojom::FocusType::kBackward &&
535 focus_type != blink::mojom::FocusType::kForward) {
539 const int modifiers = focus_type == blink::mojom::FocusType::kForward
540 ? blink::WebInputEvent::kNoModifiers
541 : blink::WebInputEvent::kShiftKey;
543 blink::WebKeyboardEvent simulated_event(blink::WebInputEvent::Type::kKeyDown,
544 modifiers, base::TimeTicks());
545 simulated_event.windows_key_code = ui::KeyboardCode::VKEY_TAB;
546 HandleWebInputEvent(simulated_event);
549 void PdfViewWebPlugin::UpdateVisibility(bool visibility) {}
551 blink::WebInputEventResult PdfViewWebPlugin::HandleInputEvent(
552 const blink::WebCoalescedInputEvent& event,
553 ui::Cursor* cursor) {
554 const blink::WebInputEventResult result =
555 HandleWebInputEvent(event.Event())
556 ? blink::WebInputEventResult::kHandledApplication
557 : blink::WebInputEventResult::kNotHandled;
559 *cursor = cursor_type_;
564 void PdfViewWebPlugin::DidReceiveResponse(
565 const blink::WebURLResponse& response) {}
567 void PdfViewWebPlugin::DidReceiveData(const char* data, size_t data_length) {}
569 void PdfViewWebPlugin::DidFinishLoading() {}
571 void PdfViewWebPlugin::DidFailLoading(const blink::WebURLError& error) {}
573 bool PdfViewWebPlugin::SupportsPaginatedPrint() {
577 bool PdfViewWebPlugin::GetPrintPresetOptionsFromDocument(
578 blink::WebPrintPresetOptions* print_preset_options) {
579 print_preset_options->is_scaling_disabled = !engine_->GetPrintScaling();
580 print_preset_options->copies = engine_->GetCopiesToPrint();
581 print_preset_options->duplex_mode = engine_->GetDuplexMode();
582 print_preset_options->uniform_page_size = engine_->GetUniformPageSizePoints();
586 int PdfViewWebPlugin::PrintBegin(const blink::WebPrintParams& print_params) {
587 // The returned value is always equal to the number of pages in the PDF
588 // document irrespective of the printable area.
589 int32_t ret = engine_->GetNumberOfPages();
593 if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality))
596 print_params_ = print_params;
597 if (!engine_->HasPermission(DocumentPermission::kPrintHighQuality))
598 print_params_->rasterize_pdf = true;
600 engine_->PrintBegin();
604 void PdfViewWebPlugin::PrintPage(int page_number, cc::PaintCanvas* canvas) {
605 // The entire document goes into one metafile. However, it is impossible to
606 // know if a call to `PrintPage()` is the last call. Thus, `PrintPage()` just
607 // stores the pages to print and the metafile. Eventually, the printed output
608 // is generated in `PrintEnd()` and copied over to the metafile.
610 // Every `canvas` passed to this method should have a valid `metafile`.
611 printing::MetafileSkia* metafile = canvas->GetPrintingMetafile();
614 // `pages_to_print_` should be empty iff `printing_metafile_` is not set.
615 DCHECK_EQ(pages_to_print_.empty(), !printing_metafile_);
617 // The metafile should be the same across all calls for a given print job.
618 DCHECK(!printing_metafile_ || (printing_metafile_ == metafile));
620 if (!printing_metafile_)
621 printing_metafile_ = metafile;
623 pages_to_print_.push_back(page_number);
626 void PdfViewWebPlugin::PrintEnd() {
627 if (pages_to_print_.empty())
630 print_pages_called_ = true;
631 printing_metafile_->InitFromData(
632 engine_->PrintPages(pages_to_print_, print_params_.value()));
634 if (print_pages_called_)
635 client_->RecordComputedAction("PDF.PrintPage");
636 print_pages_called_ = false;
637 print_params_.reset();
640 printing_metafile_ = nullptr;
641 pages_to_print_.clear();
644 bool PdfViewWebPlugin::HasSelection() const {
645 return !selected_text_.IsEmpty();
648 blink::WebString PdfViewWebPlugin::SelectionAsText() const {
649 return selected_text_;
652 blink::WebString PdfViewWebPlugin::SelectionAsMarkup() const {
653 return selected_text_;
656 bool PdfViewWebPlugin::CanEditText() const {
657 return engine_->CanEditText();
660 bool PdfViewWebPlugin::HasEditableText() const {
661 return engine_->HasEditableText();
664 bool PdfViewWebPlugin::CanUndo() const {
665 return engine_->CanUndo();
668 bool PdfViewWebPlugin::CanRedo() const {
669 return engine_->CanRedo();
672 bool PdfViewWebPlugin::CanCopy() const {
673 return engine_->HasPermission(DocumentPermission::kCopy);
676 bool PdfViewWebPlugin::ExecuteEditCommand(const blink::WebString& name,
677 const blink::WebString& value) {
678 if (name == "SelectAll")
684 if (name == "Paste" || name == "PasteAndMatchStyle")
696 blink::WebURL PdfViewWebPlugin::LinkAtPosition(
697 const gfx::Point& /*position*/) const {
698 return GURL(link_under_cursor_);
701 bool PdfViewWebPlugin::StartFind(const blink::WebString& search_text,
704 ResetRecentlySentFindUpdate();
705 find_identifier_ = identifier;
706 engine_->StartFind(search_text.Utf16(), case_sensitive);
710 void PdfViewWebPlugin::SelectFindResult(bool forward, int identifier) {
711 find_identifier_ = identifier;
712 engine_->SelectFindResult(forward);
715 void PdfViewWebPlugin::StopFind() {
716 find_identifier_ = -1;
719 client_->ReportFindInPageTickmarks(tickmarks_);
722 bool PdfViewWebPlugin::CanRotateView() {
723 return !IsPrintPreview();
726 void PdfViewWebPlugin::RotateView(blink::WebPlugin::RotationType type) {
727 DCHECK(CanRotateView());
730 case blink::WebPlugin::RotationType::k90Clockwise:
731 engine_->RotateClockwise();
733 case blink::WebPlugin::RotationType::k90Counterclockwise:
734 engine_->RotateCounterclockwise();
739 bool PdfViewWebPlugin::ShouldDispatchImeEventsToPlugin() {
743 blink::WebTextInputType PdfViewWebPlugin::GetPluginTextInputType() {
744 return text_input_type_;
747 gfx::Rect PdfViewWebPlugin::GetPluginCaretBounds() {
751 void PdfViewWebPlugin::ImeSetCompositionForPlugin(
752 const blink::WebString& text,
753 const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
754 const gfx::Range& /*replacement_range*/,
755 int /*selection_start*/,
756 int /*selection_end*/) {
757 composition_text_ = text;
760 void PdfViewWebPlugin::ImeCommitTextForPlugin(
761 const blink::WebString& text,
762 const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
763 const gfx::Range& /*replacement_range*/,
764 int /*relative_cursor_pos*/) {
765 HandleImeCommit(text);
768 void PdfViewWebPlugin::ImeFinishComposingTextForPlugin(
769 bool /*keep_selection*/) {
770 HandleImeCommit(composition_text_);
773 void PdfViewWebPlugin::ProposeDocumentLayout(const DocumentLayout& layout) {
774 base::Value::Dict message;
775 message.Set("type", "documentDimensions");
776 message.Set("width", layout.size().width());
777 message.Set("height", layout.size().height());
778 message.Set("layoutOptions", layout.options().ToValue());
779 base::Value::List page_dimensions;
780 for (size_t i = 0; i < layout.page_count(); ++i)
781 page_dimensions.Append(DictFromRect(layout.page_rect(i)));
782 message.Set("pageDimensions", std::move(page_dimensions));
783 client_->PostMessage(std::move(message));
785 // Reload the accessibility tree on layout changes because the relative page
786 // bounds are no longer valid.
787 if (layout.dirty() && accessibility_state_ == AccessibilityState::kLoaded)
791 void PdfViewWebPlugin::Invalidate(const gfx::Rect& rect) {
793 deferred_invalidates_.push_back(rect);
797 gfx::Rect offset_rect = rect + available_area_.OffsetFromOrigin();
798 paint_manager_.InvalidateRect(offset_rect);
801 void PdfViewWebPlugin::DidScroll(const gfx::Vector2d& offset) {
802 if (!image_data_.drawsNothing())
803 paint_manager_.ScrollRect(available_area_, offset);
806 void PdfViewWebPlugin::ScrollToX(int x_screen_coords) {
807 const float x_scroll_pos = x_screen_coords / device_scale_;
809 base::Value::Dict message;
810 message.Set("type", "setScrollPosition");
811 message.Set("x", static_cast<double>(x_scroll_pos));
812 client_->PostMessage(std::move(message));
815 void PdfViewWebPlugin::ScrollToY(int y_screen_coords) {
816 const float y_scroll_pos = y_screen_coords / device_scale_;
818 base::Value::Dict message;
819 message.Set("type", "setScrollPosition");
820 message.Set("y", static_cast<double>(y_scroll_pos));
821 client_->PostMessage(std::move(message));
824 void PdfViewWebPlugin::ScrollBy(const gfx::Vector2d& delta) {
825 const float x_delta = delta.x() / device_scale_;
826 const float y_delta = delta.y() / device_scale_;
828 base::Value::Dict message;
829 message.Set("type", "scrollBy");
830 message.Set("x", static_cast<double>(x_delta));
831 message.Set("y", static_cast<double>(y_delta));
832 client_->PostMessage(std::move(message));
835 void PdfViewWebPlugin::ScrollToPage(int page) {
836 if (!engine_ || engine_->GetNumberOfPages() == 0)
839 base::Value::Dict message;
840 message.Set("type", "goToPage");
841 message.Set("page", page);
842 client_->PostMessage(std::move(message));
845 void PdfViewWebPlugin::NavigateTo(const std::string& url,
846 WindowOpenDisposition disposition) {
847 base::Value::Dict message;
848 message.Set("type", "navigate");
849 message.Set("url", url);
850 message.Set("disposition", static_cast<int>(disposition));
851 client_->PostMessage(std::move(message));
854 void PdfViewWebPlugin::NavigateToDestination(int page,
858 base::Value::Dict message;
859 message.Set("type", "navigateToDestination");
860 message.Set("page", page);
862 message.Set("x", static_cast<double>(*x));
864 message.Set("y", static_cast<double>(*y));
866 message.Set("zoom", static_cast<double>(*zoom));
867 client_->PostMessage(std::move(message));
870 void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) {
871 cursor_type_ = new_cursor_type;
874 void PdfViewWebPlugin::UpdateTickMarks(
875 const std::vector<gfx::Rect>& tickmarks) {
876 tickmarks_ = tickmarks;
879 void PdfViewWebPlugin::NotifyNumberOfFindResultsChanged(int total,
881 // We don't want to spam the renderer with too many updates to the number of
882 // find results. Don't send an update if we sent one too recently. If it's the
883 // final update, we always send it though.
884 if (recently_sent_find_update_ && !final_result)
887 // After stopping search and setting `find_identifier_` to -1 there still may
888 // be a NotifyNumberOfFindResultsChanged notification pending from engine.
890 if (find_identifier_ != -1) {
891 client_->ReportFindInPageMatchCount(find_identifier_, total, final_result);
894 client_->ReportFindInPageTickmarks(tickmarks_);
899 recently_sent_find_update_ = true;
900 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
902 base::BindOnce(&PdfViewWebPlugin::ResetRecentlySentFindUpdate,
903 weak_factory_.GetWeakPtr()),
904 kFindResultCooldown);
907 void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index,
909 if (find_identifier_ == -1 || !client_->PluginContainer())
912 DCHECK_GE(current_find_index, -1);
913 client_->ReportFindInPageSelection(find_identifier_, current_find_index + 1,
917 void PdfViewWebPlugin::NotifyTouchSelectionOccurred() {
918 base::Value::Dict message;
919 message.Set("type", "touchSelectionOccurred");
920 client_->PostMessage(std::move(message));
923 void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
924 caret_rect_ = caret_rect + available_area_.OffsetFromOrigin();
927 void PdfViewWebPlugin::GetDocumentPassword(
928 base::OnceCallback<void(const std::string&)> callback) {
929 DCHECK(password_callback_.is_null());
930 password_callback_ = std::move(callback);
932 base::Value::Dict message;
933 message.Set("type", "getPassword");
934 client_->PostMessage(std::move(message));
937 void PdfViewWebPlugin::Beep() {
938 base::Value::Dict message;
939 message.Set("type", "beep");
940 client_->PostMessage(std::move(message));
943 void PdfViewWebPlugin::Alert(const std::string& message) {
944 client_->Alert(blink::WebString::FromUTF8(message));
947 bool PdfViewWebPlugin::Confirm(const std::string& message) {
948 return client_->Confirm(blink::WebString::FromUTF8(message));
951 std::string PdfViewWebPlugin::Prompt(const std::string& question,
952 const std::string& default_answer) {
954 ->Prompt(blink::WebString::FromUTF8(question),
955 blink::WebString::FromUTF8(default_answer))
959 std::string PdfViewWebPlugin::GetURL() {
963 void PdfViewWebPlugin::LoadUrl(base::StringPiece url,
964 LoadUrlCallback callback) {
966 request.url = std::string(url);
967 request.method = "GET";
968 request.ignore_redirects = true;
970 auto loader = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
971 UrlLoader* raw_loader = loader.get();
972 raw_loader->Open(request,
973 base::BindOnce(std::move(callback), std::move(loader)));
976 void PdfViewWebPlugin::Email(const std::string& to,
977 const std::string& cc,
978 const std::string& bcc,
979 const std::string& subject,
980 const std::string& body) {
981 base::Value::Dict message;
982 message.Set("type", "email");
983 message.Set("to", base::EscapeUrlEncodedData(to, false));
984 message.Set("cc", base::EscapeUrlEncodedData(cc, false));
985 message.Set("bcc", base::EscapeUrlEncodedData(bcc, false));
986 message.Set("subject", base::EscapeUrlEncodedData(subject, false));
987 message.Set("body", base::EscapeUrlEncodedData(body, false));
988 client_->PostMessage(std::move(message));
991 void PdfViewWebPlugin::Print() {
995 const bool can_print =
996 engine_->HasPermission(DocumentPermission::kPrintLowQuality) ||
997 engine_->HasPermission(DocumentPermission::kPrintHighQuality);
1001 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1002 FROM_HERE, base::BindOnce(&PdfViewWebPlugin::OnInvokePrintDialog,
1003 weak_factory_.GetWeakPtr()));
1006 void PdfViewWebPlugin::SubmitForm(const std::string& url,
1009 // `url` might be a relative URL. Resolve it against the document's URL.
1010 // TODO(crbug.com/1322928): Probably redundant with `Client::CompleteURL()`.
1011 GURL resolved_url = GURL(url_).Resolve(url);
1012 if (!resolved_url.is_valid())
1016 request.url = resolved_url.spec();
1017 request.method = "POST";
1018 request.body.assign(static_cast<const char*>(data), length);
1020 form_loader_ = std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
1021 form_loader_->Open(request, base::BindOnce(&PdfViewWebPlugin::DidFormOpen,
1022 weak_factory_.GetWeakPtr()));
1025 void PdfViewWebPlugin::DidFormOpen(int32_t result) {
1026 // TODO(crbug.com/719344): Process response.
1027 LOG_IF(ERROR, result != kSuccess) << "DidFormOpen failed: " << result;
1028 form_loader_.reset();
1031 void PdfViewWebPlugin::DidStartLoading() {
1032 if (did_call_start_loading_)
1035 client_->DidStartLoading();
1036 did_call_start_loading_ = true;
1039 void PdfViewWebPlugin::DidStopLoading() {
1040 if (!did_call_start_loading_)
1043 client_->DidStopLoading();
1044 did_call_start_loading_ = false;
1047 int PdfViewWebPlugin::GetContentRestrictions() const {
1048 int content_restrictions = kContentRestrictionCut | kContentRestrictionPaste;
1049 if (!engine_->HasPermission(DocumentPermission::kCopy))
1050 content_restrictions |= kContentRestrictionCopy;
1052 if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality) &&
1053 !engine_->HasPermission(DocumentPermission::kPrintHighQuality)) {
1054 content_restrictions |= kContentRestrictionPrint;
1057 return content_restrictions;
1060 std::unique_ptr<UrlLoader> PdfViewWebPlugin::CreateUrlLoader() {
1064 // Disable save and print until the document is fully loaded, since they
1065 // would generate an incomplete document. This needs to be done each time
1066 // DidStartLoading() is called because that resets the content restrictions.
1067 pdf_service_->UpdateContentRestrictions(kContentRestrictionSave |
1068 kContentRestrictionPrint);
1071 return std::make_unique<UrlLoader>(weak_factory_.GetWeakPtr());
1074 std::vector<PDFEngine::Client::SearchStringResult>
1075 PdfViewWebPlugin::SearchString(const char16_t* string,
1076 const char16_t* term,
1077 bool case_sensitive) {
1078 base::i18n::RepeatingStringSearch searcher(
1079 /*find_this=*/term, /*in_this=*/string, case_sensitive);
1080 std::vector<SearchStringResult> results;
1083 while (searcher.NextMatchResult(match_index, match_length))
1084 results.push_back({.start_index = match_index, .length = match_length});
1088 void PdfViewWebPlugin::DocumentLoadComplete() {
1089 DCHECK_EQ(DocumentLoadState::kLoading, document_load_state_);
1090 document_load_state_ = DocumentLoadState::kComplete;
1092 client_->RecordComputedAction("PDF.LoadSuccess");
1094 // Clear the focus state for on-screen keyboards.
1095 FormFieldFocusChange(PDFEngine::FocusFieldType::kNoFocus);
1097 if (IsPrintPreview()) {
1098 // Scroll location is retained across document loads in Print Preview, so
1099 // there's no need to override the scroll position by scrolling again.
1100 if (IsPreviewingPDF(print_preview_page_count_)) {
1101 SendPrintPreviewLoadedNotification();
1103 DCHECK_EQ(0, print_preview_loaded_page_count_);
1104 print_preview_loaded_page_count_ = 1;
1105 engine_->AppendBlankPages(print_preview_page_count_);
1106 LoadNextPreviewPage();
1109 OnGeometryChanged(0, 0);
1110 if (!document_size_.IsEmpty())
1111 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1114 RecordDocumentMetrics();
1116 if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfPortfolio)) {
1122 if (accessibility_state_ == AccessibilityState::kPending)
1123 LoadAccessibility();
1129 pdf_service_->UpdateContentRestrictions(GetContentRestrictions());
1132 void PdfViewWebPlugin::DocumentLoadFailed() {
1133 DCHECK_EQ(DocumentLoadState::kLoading, document_load_state_);
1134 document_load_state_ = DocumentLoadState::kFailed;
1136 client_->RecordComputedAction("PDF.LoadFailure");
1138 // Send a progress value of -1 to indicate a failure.
1139 SendLoadingProgress(-1);
1143 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1146 void PdfViewWebPlugin::DocumentHasUnsupportedFeature(
1147 const std::string& feature) {
1148 DCHECK(!feature.empty());
1149 std::string metric = base::StrCat({"PDF_Unsupported_", feature});
1150 if (unsupported_features_reported_.insert(metric).second)
1151 client_->RecordComputedAction(metric);
1153 if (!full_frame_ || notified_browser_about_unsupported_feature_)
1156 notified_browser_about_unsupported_feature_ = true;
1157 pdf_service_->HasUnsupportedFeature();
1160 void PdfViewWebPlugin::DocumentLoadProgress(uint32_t available,
1161 uint32_t doc_size) {
1162 double progress = 0.0;
1164 progress = 100.0 * static_cast<double>(available) / doc_size;
1166 // Use heuristics when the document size is unknown.
1167 // Progress logarithmically from 0 to 100M.
1168 static const double kFactor = std::log(100'000'000.0) / 100.0;
1171 std::min(std::log(static_cast<double>(available)) / kFactor, 100.0);
1174 // DocumentLoadComplete() will send the 100% load progress.
1175 if (progress >= 100)
1178 // Avoid sending too many progress messages over PostMessage.
1179 if (progress <= last_progress_sent_ + 1)
1182 SendLoadingProgress(progress);
1185 void PdfViewWebPlugin::FormFieldFocusChange(PDFEngine::FocusFieldType type) {
1186 base::Value::Dict message;
1187 message.Set("type", "formFocusChange");
1188 message.Set("focused", type != PDFEngine::FocusFieldType::kNoFocus);
1189 client_->PostMessage(std::move(message));
1191 text_input_type_ = type == PDFEngine::FocusFieldType::kText
1192 ? blink::WebTextInputType::kWebTextInputTypeText
1193 : blink::WebTextInputType::kWebTextInputTypeNone;
1194 client_->UpdateTextInputState();
1197 bool PdfViewWebPlugin::IsPrintPreview() const {
1198 return is_print_preview_;
1201 SkColor PdfViewWebPlugin::GetBackgroundColor() const {
1202 return background_color_;
1205 void PdfViewWebPlugin::SetIsSelecting(bool is_selecting) {
1206 base::Value::Dict message;
1207 message.Set("type", "setIsSelecting");
1208 message.Set("isSelecting", is_selecting);
1209 client_->PostMessage(std::move(message));
1212 void PdfViewWebPlugin::SelectionChanged(const gfx::Rect& left,
1213 const gfx::Rect& right) {
1214 gfx::PointF left_point(left.x() + available_area_.x(), left.y());
1215 gfx::PointF right_point(right.x() + available_area_.x(), right.y());
1217 const float inverse_scale = 1.0f / device_scale_;
1218 left_point.Scale(inverse_scale);
1219 right_point.Scale(inverse_scale);
1221 pdf_service_->SelectionChanged(left_point, left.height() * inverse_scale,
1222 right_point, right.height() * inverse_scale);
1224 if (accessibility_state_ == AccessibilityState::kLoaded)
1225 PrepareAndSetAccessibilityViewportInfo();
1228 void PdfViewWebPlugin::EnteredEditMode() {
1230 pdf_service_->SetPluginCanSave(true);
1232 base::Value::Dict message;
1233 message.Set("type", "setIsEditing");
1234 client_->PostMessage(std::move(message));
1237 void PdfViewWebPlugin::DocumentFocusChanged(bool document_has_focus) {
1238 base::Value::Dict message;
1239 message.Set("type", "documentFocusChanged");
1240 message.Set("hasFocus", document_has_focus);
1241 client_->PostMessage(std::move(message));
1244 void PdfViewWebPlugin::SetSelectedText(const std::string& selected_text) {
1245 selected_text_ = blink::WebString::FromUTF8(selected_text);
1246 client_->TextSelectionChanged(selected_text_, /*offset=*/0,
1247 gfx::Range(0, selected_text_.length()));
1250 void PdfViewWebPlugin::SetLinkUnderCursor(
1251 const std::string& link_under_cursor) {
1252 link_under_cursor_ = link_under_cursor;
1255 bool PdfViewWebPlugin::IsValidLink(const std::string& url) {
1256 return base::Value(url).is_string();
1259 void PdfViewWebPlugin::SetCaretPosition(const gfx::PointF& position) {
1260 engine_->SetCaretPosition(FrameToPdfCoordinates(position));
1263 void PdfViewWebPlugin::MoveRangeSelectionExtent(const gfx::PointF& extent) {
1264 engine_->MoveRangeSelectionExtent(FrameToPdfCoordinates(extent));
1267 void PdfViewWebPlugin::SetSelectionBounds(const gfx::PointF& base,
1268 const gfx::PointF& extent) {
1269 engine_->SetSelectionBounds(FrameToPdfCoordinates(base),
1270 FrameToPdfCoordinates(extent));
1273 bool PdfViewWebPlugin::IsValid() const {
1274 return client_->HasFrame();
1277 blink::WebURL PdfViewWebPlugin::CompleteURL(
1278 const blink::WebString& partial_url) const {
1280 return client_->CompleteURL(partial_url);
1283 net::SiteForCookies PdfViewWebPlugin::SiteForCookies() const {
1285 return client_->SiteForCookies();
1288 void PdfViewWebPlugin::SetReferrerForRequest(
1289 blink::WebURLRequest& request,
1290 const blink::WebURL& referrer_url) {
1291 client_->SetReferrerForRequest(request, referrer_url);
1294 std::unique_ptr<blink::WebAssociatedURLLoader>
1295 PdfViewWebPlugin::CreateAssociatedURLLoader(
1296 const blink::WebAssociatedURLLoaderOptions& options) {
1297 return client_->CreateAssociatedURLLoader(options);
1300 void PdfViewWebPlugin::OnMessage(const base::Value::Dict& message) {
1301 using MessageHandler = void (PdfViewWebPlugin::*)(const base::Value::Dict&);
1303 static constexpr auto kMessageHandlers =
1304 base::MakeFixedFlatMap<base::StringPiece, MessageHandler>({
1305 {"displayAnnotations",
1306 &PdfViewWebPlugin::HandleDisplayAnnotationsMessage},
1307 {"getNamedDestination",
1308 &PdfViewWebPlugin::HandleGetNamedDestinationMessage},
1309 {"getPageBoundingBox",
1310 &PdfViewWebPlugin::HandleGetPageBoundingBoxMessage},
1311 {"getPasswordComplete",
1312 &PdfViewWebPlugin::HandleGetPasswordCompleteMessage},
1313 {"getSelectedText", &PdfViewWebPlugin::HandleGetSelectedTextMessage},
1314 {"getThumbnail", &PdfViewWebPlugin::HandleGetThumbnailMessage},
1315 {"print", &PdfViewWebPlugin::HandlePrintMessage},
1316 {"loadPreviewPage", &PdfViewWebPlugin::HandleLoadPreviewPageMessage},
1317 {"resetPrintPreviewMode",
1318 &PdfViewWebPlugin::HandleResetPrintPreviewModeMessage},
1319 {"rotateClockwise", &PdfViewWebPlugin::HandleRotateClockwiseMessage},
1320 {"rotateCounterclockwise",
1321 &PdfViewWebPlugin::HandleRotateCounterclockwiseMessage},
1322 {"save", &PdfViewWebPlugin::HandleSaveMessage},
1323 {"saveAttachment", &PdfViewWebPlugin::HandleSaveAttachmentMessage},
1324 {"selectAll", &PdfViewWebPlugin::HandleSelectAllMessage},
1325 {"setBackgroundColor",
1326 &PdfViewWebPlugin::HandleSetBackgroundColorMessage},
1327 {"setPresentationMode",
1328 &PdfViewWebPlugin::HandleSetPresentationModeMessage},
1329 {"setTwoUpView", &PdfViewWebPlugin::HandleSetTwoUpViewMessage},
1330 {"stopScrolling", &PdfViewWebPlugin::HandleStopScrollingMessage},
1331 {"viewport", &PdfViewWebPlugin::HandleViewportMessage},
1334 MessageHandler handler = kMessageHandlers.at(*message.FindString("type"));
1335 (this->*handler)(message);
1338 void PdfViewWebPlugin::HandleDisplayAnnotationsMessage(
1339 const base::Value::Dict& message) {
1340 engine_->DisplayAnnotations(message.FindBool("display").value());
1343 void PdfViewWebPlugin::HandleGetNamedDestinationMessage(
1344 const base::Value::Dict& message) {
1345 absl::optional<PDFEngine::NamedDestination> named_destination =
1346 engine_->GetNamedDestination(*message.FindString("namedDestination"));
1348 const int page_number = named_destination.has_value()
1349 ? base::checked_cast<int>(named_destination->page)
1352 base::Value::Dict reply =
1353 PrepareReplyMessage("getNamedDestinationReply", message);
1354 reply.Set("pageNumber", page_number);
1356 if (named_destination.has_value() && !named_destination->view.empty()) {
1357 std::ostringstream view_stream;
1358 view_stream << named_destination->view;
1359 if (named_destination->xyz_params.empty()) {
1360 for (unsigned long i = 0; i < named_destination->num_params; ++i)
1361 view_stream << "," << named_destination->params[i];
1363 view_stream << "," << named_destination->xyz_params;
1366 reply.Set("namedDestinationView", view_stream.str());
1369 client_->PostMessage(std::move(reply));
1372 void PdfViewWebPlugin::HandleGetPageBoundingBoxMessage(
1373 const base::Value::Dict& message) {
1374 const int page_index = message.FindInt("page").value();
1375 base::Value::Dict reply =
1376 PrepareReplyMessage("getPageBoundingBoxReply", message);
1378 gfx::RectF bounding_box = engine_->GetPageBoundingBox(page_index);
1379 gfx::Rect page_bounds = engine_->GetPageBoundsRect(page_index);
1381 // Flip the origin from bottom-left to top-left.
1382 bounding_box.set_y(static_cast<float>(page_bounds.height()) -
1383 bounding_box.bottom());
1384 reply.Set("x", bounding_box.x());
1385 reply.Set("y", bounding_box.y());
1386 reply.Set("width", bounding_box.width());
1387 reply.Set("height", bounding_box.height());
1389 client_->PostMessage(std::move(reply));
1392 void PdfViewWebPlugin::HandleGetPasswordCompleteMessage(
1393 const base::Value::Dict& message) {
1394 DCHECK(password_callback_);
1395 std::move(password_callback_).Run(*message.FindString("password"));
1398 void PdfViewWebPlugin::HandleGetSelectedTextMessage(
1399 const base::Value::Dict& message) {
1400 // Always return unix newlines to JavaScript.
1401 std::string selected_text;
1402 base::RemoveChars(engine_->GetSelectedText(), "\r", &selected_text);
1404 base::Value::Dict reply =
1405 PrepareReplyMessage("getSelectedTextReply", message);
1406 reply.Set("selectedText", selected_text);
1407 client_->PostMessage(std::move(reply));
1410 void PdfViewWebPlugin::HandleGetThumbnailMessage(
1411 const base::Value::Dict& message) {
1412 const int page_index = message.FindInt("page").value();
1413 base::Value::Dict reply = PrepareReplyMessage("getThumbnailReply", message);
1415 engine_->RequestThumbnail(
1416 page_index, device_scale_,
1417 base::BindOnce(&PdfViewWebPlugin::SendThumbnail,
1418 weak_factory_.GetWeakPtr(), std::move(reply)));
1421 void PdfViewWebPlugin::HandlePrintMessage(
1422 const base::Value::Dict& /*message*/) {
1426 void PdfViewWebPlugin::HandleRotateClockwiseMessage(
1427 const base::Value::Dict& /*message*/) {
1428 engine_->RotateClockwise();
1431 void PdfViewWebPlugin::HandleRotateCounterclockwiseMessage(
1432 const base::Value::Dict& /*message*/) {
1433 engine_->RotateCounterclockwise();
1436 void PdfViewWebPlugin::HandleSaveAttachmentMessage(
1437 const base::Value::Dict& message) {
1438 const int index = message.FindInt("attachmentIndex").value();
1440 const std::vector<DocumentAttachmentInfo>& list =
1441 engine_->GetDocumentAttachmentInfoList();
1442 DCHECK_GE(index, 0);
1443 DCHECK_LT(static_cast<size_t>(index), list.size());
1444 DCHECK(list[index].is_readable);
1445 DCHECK(IsSaveDataSizeValid(list[index].size_bytes));
1447 std::vector<uint8_t> data = engine_->GetAttachmentData(index);
1448 base::Value data_to_save(
1449 IsSaveDataSizeValid(data.size()) ? data : std::vector<uint8_t>());
1451 base::Value::Dict reply = PrepareReplyMessage("saveAttachmentReply", message);
1452 reply.Set("dataToSave", std::move(data_to_save));
1453 client_->PostMessage(std::move(reply));
1456 void PdfViewWebPlugin::HandleSaveMessage(const base::Value::Dict& message) {
1457 const std::string& token = *message.FindString("token");
1458 int request_type = message.FindInt("saveRequestType").value();
1459 DCHECK_GE(request_type, static_cast<int>(SaveRequestType::kAnnotation));
1460 DCHECK_LE(request_type, static_cast<int>(SaveRequestType::kEdited));
1462 switch (static_cast<SaveRequestType>(request_type)) {
1463 case SaveRequestType::kAnnotation:
1464 #if BUILDFLAG(ENABLE_INK)
1465 // In annotation mode, assume the user will make edits and prefer saving
1466 // using the plugin data.
1467 pdf_service_->SetPluginCanSave(true);
1468 SaveToBuffer(token);
1471 #endif // BUILDFLAG(ENABLE_INK)
1473 case SaveRequestType::kOriginal:
1474 pdf_service_->SetPluginCanSave(false);
1476 pdf_service_->SetPluginCanSave(edit_mode_);
1478 case SaveRequestType::kEdited:
1479 SaveToBuffer(token);
1484 void PdfViewWebPlugin::HandleSelectAllMessage(
1485 const base::Value::Dict& /*message*/) {
1486 engine_->SelectAll();
1489 void PdfViewWebPlugin::HandleSetBackgroundColorMessage(
1490 const base::Value::Dict& message) {
1492 base::checked_cast<SkColor>(message.FindDouble("color").value());
1495 void PdfViewWebPlugin::HandleSetPresentationModeMessage(
1496 const base::Value::Dict& message) {
1497 engine_->SetReadOnly(message.FindBool("enablePresentationMode").value());
1500 void PdfViewWebPlugin::HandleSetTwoUpViewMessage(
1501 const base::Value::Dict& message) {
1502 engine_->SetDocumentLayout(message.FindBool("enableTwoUpView").value()
1503 ? DocumentLayout::PageSpread::kTwoUpOdd
1504 : DocumentLayout::PageSpread::kOneUp);
1507 void PdfViewWebPlugin::HandleStopScrollingMessage(
1508 const base::Value::Dict& /*message*/) {
1509 stop_scrolling_ = true;
1512 void PdfViewWebPlugin::HandleViewportMessage(const base::Value::Dict& message) {
1513 const base::Value::Dict* layout_options_value =
1514 message.FindDict("layoutOptions");
1515 if (layout_options_value) {
1516 DocumentLayout::Options layout_options;
1517 layout_options.FromValue(*layout_options_value);
1519 ui_direction_ = layout_options.direction();
1521 // TODO(crbug.com/1013800): Eliminate need to get document size from here.
1522 document_size_ = engine_->ApplyDocumentLayout(layout_options);
1524 OnGeometryChanged(zoom_, device_scale_);
1525 if (!document_size_.IsEmpty())
1526 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1528 // Send 100% loading progress only after initial layout negotiated.
1529 if (last_progress_sent_ < 100 &&
1530 document_load_state_ == DocumentLoadState::kComplete) {
1531 SendLoadingProgress(/*percentage=*/100);
1535 gfx::Vector2dF scroll_offset(*message.FindDouble("xOffset"),
1536 *message.FindDouble("yOffset"));
1537 double new_zoom = *message.FindDouble("zoom");
1538 const PinchPhase pinch_phase =
1539 static_cast<PinchPhase>(*message.FindInt("pinchPhase"));
1541 received_viewport_message_ = true;
1542 stop_scrolling_ = false;
1543 const double zoom_ratio = new_zoom / zoom_;
1545 if (pinch_phase == PinchPhase::kStart) {
1546 scroll_offset_at_last_raster_ = scroll_offset;
1547 last_bitmap_smaller_ = false;
1548 needs_reraster_ = false;
1552 // When zooming in, we set a layer transform to avoid unneeded rerasters.
1553 // Also, if we're zooming out and the last time we rerastered was when
1554 // we were even further zoomed out (i.e. we pinch zoomed in and are now
1555 // pinch zooming back out in the same gesture), we update the layer
1556 // transform instead of rerastering.
1557 if (pinch_phase == PinchPhase::kUpdateZoomIn ||
1558 (pinch_phase == PinchPhase::kUpdateZoomOut && zoom_ratio > 1.0)) {
1559 // Get the coordinates of the center of the pinch gesture.
1560 const double pinch_x = *message.FindDouble("pinchX");
1561 const double pinch_y = *message.FindDouble("pinchY");
1562 gfx::Point pinch_center(pinch_x, pinch_y);
1564 // Get the pinch vector which represents the panning caused by the change in
1565 // pinch center between the start and the end of the gesture.
1566 const double pinch_vector_x = *message.FindDouble("pinchVectorX");
1567 const double pinch_vector_y = *message.FindDouble("pinchVectorY");
1568 gfx::Vector2d pinch_vector =
1569 gfx::Vector2d(pinch_vector_x * zoom_ratio, pinch_vector_y * zoom_ratio);
1571 gfx::Vector2d scroll_delta;
1572 // If the rendered document doesn't fill the display area we will
1573 // use `paint_offset` to anchor the paint vertically into the same place.
1574 // We use the scroll bars instead of the pinch vector to get the actual
1575 // position on screen of the paint.
1576 gfx::Vector2d paint_offset;
1578 if (plugin_rect_.width() > GetDocumentPixelWidth() * zoom_ratio) {
1579 // We want to keep the paint in the middle but it must stay in the same
1580 // position relative to the scroll bars.
1581 paint_offset = gfx::Vector2d(0, (1 - zoom_ratio) * pinch_center.y());
1582 scroll_delta = gfx::Vector2d(
1584 (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
1586 pinch_vector = gfx::Vector2d();
1587 last_bitmap_smaller_ = true;
1588 } else if (last_bitmap_smaller_) {
1589 // When the document width covers the display area's width, we will anchor
1590 // the scroll bars disregarding where the actual pinch certer is.
1591 pinch_center = gfx::Point((plugin_rect_.width() / device_scale_) / 2,
1592 (plugin_rect_.height() / device_scale_) / 2);
1593 const double zoom_when_doc_covers_plugin_width =
1594 zoom_ * plugin_rect_.width() / GetDocumentPixelWidth();
1595 paint_offset = gfx::Vector2d(
1596 (1 - new_zoom / zoom_when_doc_covers_plugin_width) * pinch_center.x(),
1597 (1 - zoom_ratio) * pinch_center.y());
1598 pinch_vector = gfx::Vector2d();
1599 scroll_delta = gfx::Vector2d(
1600 (scroll_offset.x() - scroll_offset_at_last_raster_.x() * zoom_ratio),
1601 (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
1604 paint_manager_.SetTransform(zoom_ratio, pinch_center,
1605 pinch_vector + paint_offset + scroll_delta,
1607 needs_reraster_ = false;
1611 if (pinch_phase == PinchPhase::kUpdateZoomOut ||
1612 pinch_phase == PinchPhase::kEnd) {
1613 // We reraster on pinch zoom out in order to solve the invalid regions
1614 // that appear after zooming out.
1615 // On pinch end the scale is again 1.f and we request a reraster
1616 // in the new position.
1617 paint_manager_.ClearTransform();
1618 last_bitmap_smaller_ = false;
1619 needs_reraster_ = true;
1621 // If we're rerastering due to zooming out, we need to update the scroll
1622 // offset for the last raster, in case the user continues the gesture by
1624 scroll_offset_at_last_raster_ = scroll_offset;
1627 // Bound the input parameters.
1628 new_zoom = std::max(kMinZoom, new_zoom);
1629 DCHECK(message.FindBool("userInitiated").has_value());
1631 double old_zoom = zoom_;
1634 OnGeometryChanged(old_zoom, device_scale_);
1635 if (!document_size_.IsEmpty())
1636 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
1638 UpdateScroll(GetScrollPositionFromOffset(scroll_offset));
1641 void PdfViewWebPlugin::SaveToBuffer(const std::string& token) {
1642 engine_->KillFormFocus();
1644 base::Value::Dict message;
1645 message.Set("type", "saveData");
1646 message.Set("token", token);
1647 message.Set("fileName", GetFileNameForSaveFromUrl(url_));
1649 // Expose `edit_mode_` state for integration testing.
1650 message.Set("editModeForTesting", edit_mode_);
1652 base::Value data_to_save;
1654 base::Value::BlobStorage data = engine_->GetSaveData();
1655 if (IsSaveDataSizeValid(data.size()))
1656 data_to_save = base::Value(std::move(data));
1658 #if BUILDFLAG(ENABLE_INK)
1659 uint32_t length = engine_->GetLoadedByteSize();
1660 if (IsSaveDataSizeValid(length)) {
1661 base::Value::BlobStorage data(length);
1662 if (engine_->ReadLoadedBytes(length, data.data()))
1663 data_to_save = base::Value(std::move(data));
1667 #endif // BUILDFLAG(ENABLE_INK)
1670 message.Set("dataToSave", std::move(data_to_save));
1671 client_->PostMessage(std::move(message));
1674 void PdfViewWebPlugin::SaveToFile(const std::string& token) {
1675 engine_->KillFormFocus();
1677 base::Value::Dict message;
1678 message.Set("type", "consumeSaveToken");
1679 message.Set("token", token);
1680 client_->PostMessage(std::move(message));
1682 pdf_service_->SaveUrlAs(GURL(url_), network::mojom::ReferrerPolicy::kDefault);
1685 void PdfViewWebPlugin::InvalidatePluginContainer() {
1686 client_->Invalidate();
1689 void PdfViewWebPlugin::OnPaint(const std::vector<gfx::Rect>& paint_rects,
1690 std::vector<PaintReadyRect>& ready,
1691 std::vector<gfx::Rect>& pending) {
1692 base::AutoReset<bool> auto_reset_in_paint(&in_paint_, true);
1693 DoPaint(paint_rects, ready, pending);
1696 gfx::PointF PdfViewWebPlugin::GetScrollPositionFromOffset(
1697 const gfx::Vector2dF& scroll_offset) const {
1698 gfx::PointF scroll_origin;
1700 // TODO(crbug.com/1140374): Right-to-left scrolling currently is not
1701 // compatible with the PDF viewer's sticky "scroller" element.
1702 if (ui_direction_ == base::i18n::RIGHT_TO_LEFT && IsPrintPreview()) {
1703 scroll_origin.set_x(
1704 std::max(document_size_.width() * static_cast<float>(zoom_) -
1705 plugin_dip_size_.width(),
1709 return scroll_origin + scroll_offset;
1712 void PdfViewWebPlugin::DoPaint(const std::vector<gfx::Rect>& paint_rects,
1713 std::vector<PaintReadyRect>& ready,
1714 std::vector<gfx::Rect>& pending) {
1715 if (image_data_.drawsNothing()) {
1716 DCHECK(plugin_rect_.IsEmpty());
1720 PrepareForFirstPaint(ready);
1722 if (!received_viewport_message_ || !needs_reraster_)
1725 engine_->PrePaint();
1727 std::vector<gfx::Rect> ready_rects;
1728 for (const gfx::Rect& paint_rect : paint_rects) {
1729 // Intersect with plugin area since there could be pending invalidates from
1730 // when the plugin area was larger.
1732 gfx::IntersectRects(paint_rect, gfx::Rect(plugin_rect_.size()));
1736 // Paint the rendering of the PDF document.
1737 gfx::Rect pdf_rect = gfx::IntersectRects(rect, available_area_);
1738 if (!pdf_rect.IsEmpty()) {
1739 pdf_rect.Offset(-available_area_.x(), 0);
1741 std::vector<gfx::Rect> pdf_ready;
1742 std::vector<gfx::Rect> pdf_pending;
1743 engine_->Paint(pdf_rect, image_data_, pdf_ready, pdf_pending);
1744 for (gfx::Rect& ready_rect : pdf_ready) {
1745 ready_rect.Offset(available_area_.OffsetFromOrigin());
1746 ready_rects.push_back(ready_rect);
1748 for (gfx::Rect& pending_rect : pdf_pending) {
1749 pending_rect.Offset(available_area_.OffsetFromOrigin());
1750 pending.push_back(pending_rect);
1754 // Ensure the region above the first page (if any) is filled;
1755 const int32_t first_page_ypos = 0 == engine_->GetNumberOfPages()
1757 : engine_->GetPageScreenRect(0).y();
1758 if (rect.y() < first_page_ypos) {
1759 gfx::Rect region = gfx::IntersectRects(
1760 rect, gfx::Rect(gfx::Size(plugin_rect_.width(), first_page_ypos)));
1761 image_data_.erase(GetBackgroundColor(), gfx::RectToSkIRect(region));
1762 ready_rects.push_back(region);
1765 // Ensure the background parts are filled.
1766 for (const BackgroundPart& background_part : background_parts_) {
1767 gfx::Rect intersection =
1768 gfx::IntersectRects(background_part.location, rect);
1769 if (!intersection.IsEmpty()) {
1770 image_data_.erase(background_part.color,
1771 gfx::RectToSkIRect(intersection));
1772 ready_rects.push_back(intersection);
1777 engine_->PostPaint();
1779 // TODO(crbug.com/1263614): Write pixels directly to the `SkSurface` in
1780 // `PaintManager`, rather than using an intermediate `SkBitmap` and `SkImage`.
1781 sk_sp<SkImage> painted_image = image_data_.asImage();
1782 for (const gfx::Rect& ready_rect : ready_rects)
1783 ready.emplace_back(ready_rect, painted_image);
1785 InvalidateAfterPaintDone();
1788 void PdfViewWebPlugin::PrepareForFirstPaint(
1789 std::vector<PaintReadyRect>& ready) {
1793 // Fill the image data buffer with the background color.
1794 first_paint_ = false;
1795 image_data_.eraseColor(background_color_);
1796 ready.emplace_back(gfx::SkIRectToRect(image_data_.bounds()),
1797 image_data_.asImage(), /*flush_now=*/true);
1800 void PdfViewWebPlugin::OnGeometryChanged(double old_zoom,
1801 float old_device_scale) {
1802 RecalculateAreas(old_zoom, old_device_scale);
1804 if (accessibility_state_ == AccessibilityState::kLoaded)
1805 PrepareAndSetAccessibilityViewportInfo();
1808 void PdfViewWebPlugin::RecalculateAreas(double old_zoom,
1809 float old_device_scale) {
1810 if (zoom_ != old_zoom || device_scale_ != old_device_scale)
1811 engine_->ZoomUpdated(zoom_ * device_scale_);
1813 available_area_ = gfx::Rect(plugin_rect_.size());
1814 int doc_width = GetDocumentPixelWidth();
1815 if (doc_width < available_area_.width()) {
1816 // Center the document horizontally inside the plugin rectangle.
1817 available_area_.Offset((plugin_rect_.width() - doc_width) / 2, 0);
1818 available_area_.set_width(doc_width);
1821 // The distance between top of the plugin and the bottom of the document in
1823 int bottom_of_document = GetDocumentPixelHeight();
1824 if (bottom_of_document < plugin_rect_.height())
1825 available_area_.set_height(bottom_of_document);
1827 CalculateBackgroundParts();
1829 engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
1830 engine_->PluginSizeUpdated(available_area_.size());
1833 void PdfViewWebPlugin::CalculateBackgroundParts() {
1834 background_parts_.clear();
1835 int left_width = available_area_.x();
1836 int right_start = available_area_.right();
1837 int right_width = std::abs(plugin_rect_.width() - available_area_.right());
1838 int bottom = std::min(available_area_.bottom(), plugin_rect_.height());
1840 // Note: we assume the display of the PDF document is always centered
1841 // horizontally, but not necessarily centered vertically.
1842 // Add the left rectangle.
1843 BackgroundPart part = {gfx::Rect(left_width, bottom), GetBackgroundColor()};
1844 if (!part.location.IsEmpty())
1845 background_parts_.push_back(part);
1847 // Add the right rectangle.
1848 part.location = gfx::Rect(right_start, 0, right_width, bottom);
1849 if (!part.location.IsEmpty())
1850 background_parts_.push_back(part);
1852 // Add the bottom rectangle.
1853 part.location = gfx::Rect(0, bottom, plugin_rect_.width(),
1854 plugin_rect_.height() - bottom);
1855 if (!part.location.IsEmpty())
1856 background_parts_.push_back(part);
1859 int PdfViewWebPlugin::GetDocumentPixelWidth() const {
1860 return static_cast<int>(
1861 std::ceil(document_size_.width() * zoom_ * device_scale_));
1864 int PdfViewWebPlugin::GetDocumentPixelHeight() const {
1865 return static_cast<int>(
1866 std::ceil(document_size_.height() * zoom_ * device_scale_));
1869 void PdfViewWebPlugin::InvalidateAfterPaintDone() {
1870 if (deferred_invalidates_.empty())
1873 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1874 FROM_HERE, base::BindOnce(&PdfViewWebPlugin::ClearDeferredInvalidates,
1875 weak_factory_.GetWeakPtr()));
1878 void PdfViewWebPlugin::ClearDeferredInvalidates() {
1880 for (const gfx::Rect& rect : deferred_invalidates_)
1882 deferred_invalidates_.clear();
1885 void PdfViewWebPlugin::UpdateSnapshot(sk_sp<SkImage> snapshot) {
1886 // Every time something changes (e.g. scale or scroll position),
1887 // `UpdateSnapshot()` is called, so the snapshot is effectively used only
1888 // once. Make it "no-cache" so that the old snapshots are not cached
1891 // Otherwise, for instance when scrolling, all the previous snapshots end up
1892 // accumulating in the (for the GPU path) GpuImageDecodeCache, and then in the
1893 // service transfer cache. The size of the service transfer cache is bounded,
1894 // so on desktop this "only" causes a 256MiB memory spike, but it's completely
1895 // wasted memory nonetheless.
1897 cc::PaintImageBuilder::WithDefault()
1898 .set_image(std::move(snapshot), cc::PaintImage::GetNextContentId())
1899 .set_id(cc::PaintImage::GetNextId())
1903 if (!plugin_rect_.IsEmpty())
1904 InvalidatePluginContainer();
1907 void PdfViewWebPlugin::UpdateScaledValues() {
1908 total_translate_ = snapshot_translate_;
1910 if (viewport_to_dip_scale_ != 1.0f)
1911 total_translate_.Scale(1.0f / viewport_to_dip_scale_);
1914 void PdfViewWebPlugin::UpdateScale(float scale) {
1915 if (scale <= 0.0f) {
1920 viewport_to_dip_scale_ = scale;
1921 UpdateScaledValues();
1924 void PdfViewWebPlugin::UpdateLayerTransform(float scale,
1925 const gfx::Vector2dF& translate) {
1926 snapshot_translate_ = translate;
1927 snapshot_scale_ = scale;
1928 UpdateScaledValues();
1931 void PdfViewWebPlugin::EnableAccessibility() {
1932 if (accessibility_state_ == AccessibilityState::kLoaded)
1935 LoadOrReloadAccessibility();
1938 SkBitmap PdfViewWebPlugin::GetImageForOcr(int32_t page_index,
1939 int32_t page_object_index) {
1940 return engine_->GetImageForOcr(page_index, page_object_index);
1943 void PdfViewWebPlugin::HandleAccessibilityAction(
1944 const AccessibilityActionData& action_data) {
1945 engine_->HandleAccessibilityAction(action_data);
1948 void PdfViewWebPlugin::LoadOrReloadAccessibility() {
1949 if (accessibility_state_ == AccessibilityState::kOff) {
1950 accessibility_state_ = AccessibilityState::kPending;
1953 if (document_load_state_ == DocumentLoadState::kComplete) {
1954 LoadAccessibility();
1958 void PdfViewWebPlugin::OnViewportChanged(
1959 const gfx::Rect& new_plugin_rect_in_css_pixel,
1960 float new_device_scale) {
1961 DCHECK_GT(new_device_scale, 0.0f);
1963 css_plugin_rect_ = new_plugin_rect_in_css_pixel;
1965 if (new_device_scale == device_scale_ &&
1966 new_plugin_rect_in_css_pixel == plugin_rect_) {
1970 const float old_device_scale = device_scale_;
1971 device_scale_ = new_device_scale;
1972 plugin_rect_ = new_plugin_rect_in_css_pixel;
1974 // TODO(crbug.com/1250173): We should try to avoid the downscaling in this
1975 // calculation, perhaps by migrating off `plugin_dip_size_`.
1976 plugin_dip_size_ = gfx::ScaleToEnclosingRect(new_plugin_rect_in_css_pixel,
1977 1.0f / new_device_scale)
1980 paint_manager_.SetSize(plugin_rect_.size(), device_scale_);
1982 // Initialize the image data buffer if the context size changes.
1983 const gfx::Size old_image_size = gfx::SkISizeToSize(image_data_.dimensions());
1984 const gfx::Size new_image_size =
1985 PaintManager::GetNewContextSize(old_image_size, plugin_rect_.size());
1986 if (new_image_size != old_image_size) {
1987 image_data_.allocPixels(
1988 SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(new_image_size)));
1989 first_paint_ = true;
1992 // Skip updating the geometry if the new image data buffer is empty.
1993 if (image_data_.drawsNothing())
1996 OnGeometryChanged(zoom_, old_device_scale);
1999 bool PdfViewWebPlugin::SelectAll() {
2000 engine_->SelectAll();
2004 bool PdfViewWebPlugin::Cut() {
2005 if (!HasSelection() || !CanEditText())
2008 engine_->ReplaceSelection("");
2012 bool PdfViewWebPlugin::Paste(const blink::WebString& value) {
2016 engine_->ReplaceSelection(value.Utf8());
2020 bool PdfViewWebPlugin::Undo() {
2028 bool PdfViewWebPlugin::Redo() {
2036 bool PdfViewWebPlugin::HandleWebInputEvent(const blink::WebInputEvent& event) {
2037 // Ignore user input in read-only mode.
2038 if (engine_->IsReadOnly())
2041 // `engine_` expects input events in device coordinates.
2042 float viewport_to_device_scale = viewport_to_dip_scale_ * device_scale_;
2043 std::unique_ptr<blink::WebInputEvent> transformed_event =
2044 ui::TranslateAndScaleWebInputEvent(
2046 gfx::Vector2dF(-available_area_.x() / viewport_to_device_scale, 0),
2047 viewport_to_device_scale);
2049 const blink::WebInputEvent& event_to_handle =
2050 transformed_event ? *transformed_event : event;
2052 if (engine_->HandleInputEvent(event_to_handle))
2055 // Middle click is used for scrolling and is handled by the container page.
2056 if (blink::WebInputEvent::IsMouseEventType(event_to_handle.GetType()) &&
2057 static_cast<const blink::WebMouseEvent&>(event_to_handle).button ==
2058 blink::WebPointerProperties::Button::kMiddle) {
2062 // Return true for unhandled clicks so the plugin takes focus.
2063 return event_to_handle.GetType() == blink::WebInputEvent::Type::kMouseDown;
2066 void PdfViewWebPlugin::HandleImeCommit(const blink::WebString& text) {
2070 std::u16string text16 = text.Utf16();
2071 composition_text_.Reset();
2074 for (base::i18n::UTF16CharIterator iterator(text16); iterator.Advance();) {
2075 blink::WebKeyboardEvent char_event(blink::WebInputEvent::Type::kChar,
2076 blink::WebInputEvent::kNoModifiers,
2077 ui::EventTimeForNow());
2078 char_event.windows_key_code = text16[i];
2079 char_event.native_key_code = text16[i];
2081 for (const size_t char_start = i; i < iterator.array_pos(); ++i) {
2082 char_event.text[i - char_start] = text16[i];
2083 char_event.unmodified_text[i - char_start] = text16[i];
2086 blink::WebCoalescedInputEvent input_event(char_event, ui::LatencyInfo());
2087 ui::Cursor dummy_cursor_info;
2088 HandleInputEvent(input_event, &dummy_cursor_info);
2092 void PdfViewWebPlugin::OnInvokePrintDialog() {
2096 void PdfViewWebPlugin::ResetRecentlySentFindUpdate() {
2097 recently_sent_find_update_ = false;
2100 void PdfViewWebPlugin::RecordDocumentMetrics() {
2101 if (!metrics_handler_)
2104 metrics_handler_->RecordDocumentMetrics(engine_->GetDocumentMetadata());
2105 metrics_handler_->RecordAttachmentTypes(
2106 engine_->GetDocumentAttachmentInfoList());
2109 void PdfViewWebPlugin::SendAttachments() {
2110 const std::vector<DocumentAttachmentInfo>& attachment_infos =
2111 engine_->GetDocumentAttachmentInfoList();
2112 if (attachment_infos.empty())
2115 base::Value::List attachments;
2116 for (const DocumentAttachmentInfo& attachment_info : attachment_infos) {
2117 // Send `size` as -1 to indicate that the attachment is too large to be
2119 const int size = attachment_info.size_bytes <= kMaximumSavedFileSize
2120 ? static_cast<int>(attachment_info.size_bytes)
2123 base::Value::Dict attachment;
2124 attachment.Set("name", attachment_info.name);
2125 attachment.Set("size", size);
2126 attachment.Set("readable", attachment_info.is_readable);
2127 attachments.Append(std::move(attachment));
2130 base::Value::Dict message;
2131 message.Set("type", "attachments");
2132 message.Set("attachmentsData", std::move(attachments));
2133 client_->PostMessage(std::move(message));
2136 void PdfViewWebPlugin::SendBookmarks() {
2137 base::Value::List bookmarks = engine_->GetBookmarks();
2138 if (bookmarks.empty())
2141 base::Value::Dict message;
2142 message.Set("type", "bookmarks");
2143 message.Set("bookmarksData", std::move(bookmarks));
2144 client_->PostMessage(std::move(message));
2147 void PdfViewWebPlugin::SendMetadata() {
2148 base::Value::Dict metadata;
2149 const DocumentMetadata& document_metadata = engine_->GetDocumentMetadata();
2151 const std::string version = FormatPdfVersion(document_metadata.version);
2152 if (!version.empty())
2153 metadata.Set("version", version);
2155 metadata.Set("fileSize", ui::FormatBytes(document_metadata.size_bytes));
2157 metadata.Set("linearized", document_metadata.linearized);
2159 if (!document_metadata.title.empty())
2160 metadata.Set("title", document_metadata.title);
2162 if (!document_metadata.author.empty())
2163 metadata.Set("author", document_metadata.author);
2165 if (!document_metadata.subject.empty())
2166 metadata.Set("subject", document_metadata.subject);
2168 if (!document_metadata.keywords.empty())
2169 metadata.Set("keywords", document_metadata.keywords);
2171 if (!document_metadata.creator.empty())
2172 metadata.Set("creator", document_metadata.creator);
2174 if (!document_metadata.producer.empty())
2175 metadata.Set("producer", document_metadata.producer);
2177 if (!document_metadata.creation_date.is_null()) {
2178 metadata.Set("creationDate", base::TimeFormatShortDateAndTime(
2179 document_metadata.creation_date));
2182 if (!document_metadata.mod_date.is_null()) {
2183 metadata.Set("modDate",
2184 base::TimeFormatShortDateAndTime(document_metadata.mod_date));
2187 metadata.Set("pageSize", FormatPageSize(engine_->GetUniformPageSizePoints()));
2189 metadata.Set("canSerializeDocument",
2190 IsSaveDataSizeValid(engine_->GetLoadedByteSize()));
2192 base::Value::Dict message;
2193 message.Set("type", "metadata");
2194 message.Set("metadataData", std::move(metadata));
2195 client_->PostMessage(std::move(message));
2198 void PdfViewWebPlugin::SendLoadingProgress(double percentage) {
2199 DCHECK(percentage == -1 || (percentage >= 0 && percentage <= 100));
2200 last_progress_sent_ = percentage;
2202 base::Value::Dict message;
2203 message.Set("type", "loadProgress");
2204 message.Set("progress", percentage);
2205 client_->PostMessage(std::move(message));
2208 void PdfViewWebPlugin::HandleResetPrintPreviewModeMessage(
2209 const base::Value::Dict& message) {
2210 const std::string& url = *message.FindString("url");
2211 bool is_grayscale = message.FindBool("grayscale").value();
2212 int print_preview_page_count = message.FindInt("pageCount").value();
2214 // For security reasons, crash if `url` is not for Print Preview.
2215 CHECK(IsPrintPreview());
2216 CHECK(IsPrintPreviewUrl(url));
2218 DCHECK_GE(print_preview_page_count, 0);
2220 int page_index = ExtractPrintPreviewPageIndex(url);
2221 if (IsPreviewingPDF(print_preview_page_count)) {
2222 DCHECK_EQ(page_index, kCompletePDFIndex);
2224 DCHECK_GE(page_index, 0);
2227 print_preview_page_count_ = print_preview_page_count;
2228 print_preview_loaded_page_count_ = 0;
2230 preview_pages_info_ = base::queue<PreviewPageInfo>();
2231 preview_document_load_state_ = DocumentLoadState::kComplete;
2232 document_load_state_ = DocumentLoadState::kLoading;
2233 last_progress_sent_ = 0;
2234 LoadUrl(url_, base::BindOnce(&PdfViewWebPlugin::DidOpen,
2235 weak_factory_.GetWeakPtr()));
2236 preview_engine_.reset();
2238 // TODO(crbug.com/1237952): Figure out a more consistent way to preserve
2239 // engine settings across a Print Preview reset.
2240 engine_ = client_->CreateEngine(
2241 this, PDFiumFormFiller::ScriptOption::kNoJavaScript);
2242 engine_->ZoomUpdated(zoom_ * device_scale_);
2243 engine_->PageOffsetUpdated(available_area_.OffsetFromOrigin());
2244 engine_->PluginSizeUpdated(available_area_.size());
2245 engine_->SetGrayscale(is_grayscale);
2247 paint_manager_.InvalidateRect(gfx::Rect(plugin_rect_.size()));
2250 void PdfViewWebPlugin::HandleLoadPreviewPageMessage(
2251 const base::Value::Dict& message) {
2252 const std::string& url = *message.FindString("url");
2253 int dest_page_index = message.FindInt("index").value();
2255 // For security reasons, crash if `url` is not for Print Preview.
2256 CHECK(IsPrintPreview());
2257 CHECK(IsPrintPreviewUrl(url));
2259 DCHECK_GE(dest_page_index, 0);
2260 DCHECK_LT(dest_page_index, print_preview_page_count_);
2262 // Print Preview JS will send the loadPreviewPage message for every page,
2263 // including the first page in the print preview, which has already been
2264 // loaded when handing the resetPrintPreviewMode message. Just ignore it.
2265 if (dest_page_index == 0)
2268 int src_page_index = ExtractPrintPreviewPageIndex(url);
2269 DCHECK_GE(src_page_index, 0);
2271 preview_pages_info_.push({.url = url, .dest_page_index = dest_page_index});
2272 LoadAvailablePreviewPage();
2275 void PdfViewWebPlugin::LoadAvailablePreviewPage() {
2276 if (preview_pages_info_.empty() ||
2277 document_load_state_ != DocumentLoadState::kComplete ||
2278 preview_document_load_state_ == DocumentLoadState::kLoading) {
2282 preview_document_load_state_ = DocumentLoadState::kLoading;
2283 const std::string& url = preview_pages_info_.front().url;
2285 // Note that `last_progress_sent_` is not reset for preview page loads.
2286 LoadUrl(url, base::BindOnce(&PdfViewWebPlugin::DidOpenPreview,
2287 weak_factory_.GetWeakPtr()));
2290 void PdfViewWebPlugin::DidOpenPreview(std::unique_ptr<UrlLoader> loader,
2292 DCHECK_EQ(result, kSuccess);
2294 // `preview_engine_` holds a `raw_ptr` to `preview_client_`.
2295 // We need to explicitly destroy it before clobbering
2296 // `preview_client_` to dodge lifetime issues.
2297 preview_engine_.reset();
2299 preview_client_ = std::make_unique<PreviewModeClient>(this);
2300 preview_engine_ = client_->CreateEngine(
2301 preview_client_.get(), PDFiumFormFiller::ScriptOption::kNoJavaScript);
2302 preview_engine_->PluginSizeUpdated({});
2303 preview_engine_->HandleDocumentLoad(std::move(loader), url_);
2306 void PdfViewWebPlugin::PreviewDocumentLoadComplete() {
2307 if (preview_document_load_state_ != DocumentLoadState::kLoading ||
2308 preview_pages_info_.empty()) {
2312 preview_document_load_state_ = DocumentLoadState::kComplete;
2314 int dest_page_index = preview_pages_info_.front().dest_page_index;
2315 preview_pages_info_.pop();
2316 engine_->AppendPage(preview_engine_.get(), dest_page_index);
2318 ++print_preview_loaded_page_count_;
2319 LoadNextPreviewPage();
2322 void PdfViewWebPlugin::PreviewDocumentLoadFailed() {
2323 client_->RecordComputedAction("PDF.PreviewDocumentLoadFailure");
2324 if (preview_document_load_state_ != DocumentLoadState::kLoading ||
2325 preview_pages_info_.empty()) {
2329 // Even if a print preview page failed to load, keep going.
2330 preview_document_load_state_ = DocumentLoadState::kFailed;
2331 preview_pages_info_.pop();
2332 ++print_preview_loaded_page_count_;
2333 LoadNextPreviewPage();
2336 void PdfViewWebPlugin::LoadNextPreviewPage() {
2337 if (!preview_pages_info_.empty()) {
2338 DCHECK_LT(print_preview_loaded_page_count_, print_preview_page_count_);
2339 LoadAvailablePreviewPage();
2343 if (print_preview_loaded_page_count_ == print_preview_page_count_)
2344 SendPrintPreviewLoadedNotification();
2347 void PdfViewWebPlugin::SendPrintPreviewLoadedNotification() {
2348 base::Value::Dict message;
2349 message.Set("type", "printPreviewLoaded");
2350 client_->PostMessage(std::move(message));
2353 void PdfViewWebPlugin::SendThumbnail(base::Value::Dict reply,
2354 Thumbnail thumbnail) {
2355 DCHECK_EQ(*reply.FindString("type"), "getThumbnailReply");
2356 DCHECK(reply.FindString("messageId"));
2358 reply.Set("imageData", thumbnail.TakeData());
2359 reply.Set("width", thumbnail.image_size().width());
2360 reply.Set("height", thumbnail.image_size().height());
2361 client_->PostMessage(std::move(reply));
2364 gfx::Point PdfViewWebPlugin::FrameToPdfCoordinates(
2365 const gfx::PointF& frame_coordinates) const {
2366 // TODO(crbug.com/1288847): Use methods on `blink::WebPluginContainer`.
2367 return gfx::ToFlooredPoint(
2368 gfx::ScalePoint(frame_coordinates, device_scale_)) -
2369 gfx::Vector2d(available_area_.x(), 0);
2372 AccessibilityDocInfo PdfViewWebPlugin::GetAccessibilityDocInfo() const {
2373 AccessibilityDocInfo doc_info;
2374 doc_info.page_count = engine_->GetNumberOfPages();
2375 doc_info.text_accessible =
2376 engine_->HasPermission(DocumentPermission::kCopyAccessible);
2377 doc_info.text_copyable = engine_->HasPermission(DocumentPermission::kCopy);
2381 void PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo(int32_t page_index) {
2382 // Outdated calls are ignored.
2383 if (page_index != next_accessibility_page_index_)
2385 ++next_accessibility_page_index_;
2387 AccessibilityPageInfo page_info;
2388 std::vector<AccessibilityTextRunInfo> text_runs;
2389 std::vector<AccessibilityCharInfo> chars;
2390 AccessibilityPageObjects page_objects;
2392 if (!GetAccessibilityInfo(engine_.get(), page_index, page_info, text_runs,
2393 chars, page_objects)) {
2397 pdf_accessibility_data_handler_->SetAccessibilityPageInfo(
2398 std::move(page_info), std::move(text_runs), std::move(chars),
2399 std::move(page_objects));
2401 // Schedule loading the next page.
2402 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
2404 base::BindOnce(&PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo,
2405 weak_factory_.GetWeakPtr(), page_index + 1),
2406 kAccessibilityPageDelay);
2409 void PdfViewWebPlugin::PrepareAndSetAccessibilityViewportInfo() {
2410 AccessibilityViewportInfo viewport_info;
2411 viewport_info.offset = gfx::ScaleToFlooredPoint(available_area_.origin(),
2412 1 / (device_scale_ * zoom_));
2413 viewport_info.zoom = zoom_;
2414 viewport_info.scale = device_scale_;
2415 viewport_info.focus_info = {FocusObjectType::kNone, 0, 0};
2417 engine_->GetSelection(&viewport_info.selection_start_page_index,
2418 &viewport_info.selection_start_char_index,
2419 &viewport_info.selection_end_page_index,
2420 &viewport_info.selection_end_char_index);
2422 pdf_accessibility_data_handler_->SetAccessibilityViewportInfo(
2423 std::move(viewport_info));
2426 void PdfViewWebPlugin::LoadAccessibility() {
2427 accessibility_state_ = AccessibilityState::kLoaded;
2429 // A new document layout will trigger the creation of a new accessibility
2430 // tree, so `next_accessibility_page_index_` should be reset to ignore
2431 // outdated asynchronous calls of PrepareAndSetAccessibilityPageInfo().
2432 next_accessibility_page_index_ = 0;
2433 pdf_accessibility_data_handler_->SetAccessibilityDocInfo(
2434 GetAccessibilityDocInfo());
2436 // If the document contents isn't accessible, don't send anything more.
2437 if (!(engine_->HasPermission(DocumentPermission::kCopy) ||
2438 engine_->HasPermission(DocumentPermission::kCopyAccessible))) {
2442 PrepareAndSetAccessibilityViewportInfo();
2444 // Schedule loading the first page.
2445 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
2447 base::BindOnce(&PdfViewWebPlugin::PrepareAndSetAccessibilityPageInfo,
2448 weak_factory_.GetWeakPtr(), /*page_index=*/0),
2449 kAccessibilityPageDelay);
2452 } // namespace chrome_pdf