From 606d85f2a5386472314d39923da28c70c60dc8e7 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Wed, 2 Feb 2022 12:21:57 +0100 Subject: BASELINE: Update Chromium to 96.0.4664.181 Change-Id: I762cd1da89d73aa6313b4a753fe126c34833f046 Reviewed-by: Allan Sandfeld Jensen --- chromium/pdf/BUILD.gn | 11 +- chromium/pdf/COMMON_METADATA | 3 + chromium/pdf/DEPS | 3 +- chromium/pdf/DIR_METADATA | 4 +- chromium/pdf/OWNERS | 1 - chromium/pdf/document_layout.cc | 7 + chromium/pdf/document_layout.h | 15 +- chromium/pdf/document_layout_unittest.cc | 71 ++- chromium/pdf/features.gni | 2 +- chromium/pdf/mojom/pdf.mojom | 21 + chromium/pdf/out_of_process_instance.cc | 196 ++---- chromium/pdf/out_of_process_instance.h | 20 +- chromium/pdf/pdf.h | 3 + chromium/pdf/pdf_accessibility_action_handler.h | 1 + chromium/pdf/pdf_utils/dates.cc | 4 +- chromium/pdf/pdf_view_plugin_base.cc | 327 +++++++--- chromium/pdf/pdf_view_plugin_base.h | 154 +++-- chromium/pdf/pdf_view_plugin_base_unittest.cc | 666 ++++++++++++++++----- chromium/pdf/pdf_view_web_plugin.cc | 307 ++++++---- chromium/pdf/pdf_view_web_plugin.h | 75 ++- chromium/pdf/pdf_view_web_plugin_unittest.cc | 485 ++++++++++++--- chromium/pdf/pdfium/DEPS | 1 - .../pdf/pdfium/pdfium_assert_matching_enums.cc | 2 + chromium/pdf/pdfium/pdfium_engine.cc | 22 +- chromium/pdf/pdfium/pdfium_engine_unittest.cc | 93 ++- chromium/pdf/pdfium/pdfium_form_filler.cc | 122 +++- chromium/pdf/pdfium/pdfium_form_filler.h | 47 +- chromium/pdf/pdfium/pdfium_form_filler_unittest.cc | 54 +- chromium/pdf/pdfium/pdfium_print_unittest.cc | 2 +- chromium/pdf/ppapi_migration/graphics.cc | 2 +- chromium/pdf/ppapi_migration/graphics_unittest.cc | 2 +- .../pdf/ppapi_migration/input_event_conversions.cc | 17 +- chromium/pdf/ppapi_migration/url_loader.cc | 1 - chromium/pdf/url_loader_wrapper_impl.cc | 2 +- 34 files changed, 2021 insertions(+), 722 deletions(-) create mode 100644 chromium/pdf/COMMON_METADATA (limited to 'chromium/pdf') diff --git a/chromium/pdf/BUILD.gn b/chromium/pdf/BUILD.gn index 2ebc8400391..a06ffd9e682 100644 --- a/chromium/pdf/BUILD.gn +++ b/chromium/pdf/BUILD.gn @@ -175,6 +175,7 @@ if (enable_pdf) { public_deps = [ "//printing/mojom", "//skia", + "//v8", ] deps = [ @@ -333,7 +334,6 @@ if (enable_pdf) { "//base", "//build:chromeos_buildflags", "//net", - "//pdf:buildflags", "//ppapi/cpp:objects", "//ppapi/cpp/private:internal_module", "//skia", @@ -341,6 +341,7 @@ if (enable_pdf) { "//third_party/blink/public/common:headers", "//ui/base", "//ui/base/cursor/mojom:cursor_type", + "//url", ] } @@ -401,6 +402,8 @@ if (enable_pdf) { "test/test_document_loader.h", "test/test_helpers.cc", "test/test_helpers.h", + "test/test_pdfium_engine.cc", + "test/test_pdfium_engine.h", ] configs += [ ":common" ] @@ -410,9 +413,11 @@ if (enable_pdf) { ":ppapi_migration", "//base", "//ppapi/cpp:objects", + "//testing/gmock", "//testing/gtest", - "//ui/gfx:geometry_skia", + "//third_party/blink/public/common:headers", "//ui/gfx/range", + "//ui/latency:latency", ] } @@ -498,9 +503,11 @@ if (enable_pdf) { "//third_party/blink/public/common:headers", "//third_party/pdfium", "//ui/base", + "//ui/events/blink:blink", "//ui/gfx:test_support", "//ui/gfx/geometry", "//ui/gfx/range", + "//v8", ] if (v8_use_external_startup_data) { diff --git a/chromium/pdf/COMMON_METADATA b/chromium/pdf/COMMON_METADATA new file mode 100644 index 00000000000..bfb1239ef3e --- /dev/null +++ b/chromium/pdf/COMMON_METADATA @@ -0,0 +1,3 @@ +monorail { + component: "Internals>Plugins>PDF" +} \ No newline at end of file diff --git a/chromium/pdf/DEPS b/chromium/pdf/DEPS index 5bd03ea7fde..afd5d53460d 100644 --- a/chromium/pdf/DEPS +++ b/chromium/pdf/DEPS @@ -14,11 +14,12 @@ include_rules = [ "+ui/display", "+ui/events", "+ui/gfx", - "+v8/include/v8.h" + "+v8/include", ] specific_include_rules = { ".*_unittest.*\.cc": [ "+cc/test", + "+ui/latency/latency_info.h", ], } diff --git a/chromium/pdf/DIR_METADATA b/chromium/pdf/DIR_METADATA index 8734c2bd51c..7ec54c5dcff 100644 --- a/chromium/pdf/DIR_METADATA +++ b/chromium/pdf/DIR_METADATA @@ -6,6 +6,4 @@ # For the schema of this file, see Metadata message: # https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto -monorail { - component: "Internals>Plugins>PDF" -} \ No newline at end of file +mixins: "//pdf/COMMON_METADATA" diff --git a/chromium/pdf/OWNERS b/chromium/pdf/OWNERS index 79ab573f6cd..3955287c58c 100644 --- a/chromium/pdf/OWNERS +++ b/chromium/pdf/OWNERS @@ -1,6 +1,5 @@ set noparent dhoss@chromium.org -dsinclair@chromium.org kmoon@chromium.org thestig@chromium.org diff --git a/chromium/pdf/document_layout.cc b/chromium/pdf/document_layout.cc index f13d7d066e9..756e1cb3869 100644 --- a/chromium/pdf/document_layout.cc +++ b/chromium/pdf/document_layout.cc @@ -16,6 +16,7 @@ namespace chrome_pdf { namespace { +constexpr char kDirection[] = "direction"; constexpr char kDefaultPageOrientation[] = "defaultPageOrientation"; constexpr char kTwoUpViewEnabled[] = "twoUpViewEnabled"; @@ -51,6 +52,7 @@ DocumentLayout::Options::~Options() = default; base::Value DocumentLayout::Options::ToValue() const { base::Value dictionary(base::Value::Type::DICTIONARY); + dictionary.SetIntKey(kDirection, direction_); dictionary.SetIntKey(kDefaultPageOrientation, static_cast(default_page_orientation_)); dictionary.SetBoolKey(kTwoUpViewEnabled, @@ -61,6 +63,11 @@ base::Value DocumentLayout::Options::ToValue() const { void DocumentLayout::Options::FromValue(const base::Value& value) { DCHECK(value.is_dict()); + int32_t direction = value.FindIntKey(kDirection).value(); + DCHECK_GE(direction, base::i18n::UNKNOWN_DIRECTION); + DCHECK_LE(direction, base::i18n::TEXT_DIRECTION_MAX); + direction_ = static_cast(direction); + int32_t default_page_orientation = value.FindIntKey(kDefaultPageOrientation).value(); DCHECK_GE(default_page_orientation, diff --git a/chromium/pdf/document_layout.h b/chromium/pdf/document_layout.h index 4bd6cadf658..ec57a3f0bf0 100644 --- a/chromium/pdf/document_layout.h +++ b/chromium/pdf/document_layout.h @@ -9,6 +9,7 @@ #include #include "base/check_op.h" +#include "base/i18n/rtl.h" #include "pdf/draw_utils/coordinates.h" #include "pdf/page_orientation.h" #include "ui/gfx/geometry/rect.h" @@ -46,8 +47,9 @@ class DocumentLayout final { ~Options(); friend bool operator==(const Options& lhs, const Options& rhs) { - return lhs.page_spread() == rhs.page_spread() && - lhs.default_page_orientation() == rhs.default_page_orientation(); + return lhs.direction() == rhs.direction() && + lhs.default_page_orientation() == rhs.default_page_orientation() && + lhs.page_spread() == rhs.page_spread(); } friend bool operator!=(const Options& lhs, const Options& rhs) { @@ -60,6 +62,14 @@ class DocumentLayout final { // Deserializes layout options from a base::Value. void FromValue(const base::Value& value); + // Page layout direction. This is tied to the direction of the user's UI, + // rather than the direction of individual pages. + base::i18n::TextDirection direction() const { return direction_; } + + void set_direction(base::i18n::TextDirection direction) { + direction_ = direction; + } + PageOrientation default_page_orientation() const { return default_page_orientation_; } @@ -76,6 +86,7 @@ class DocumentLayout final { void set_page_spread(PageSpread spread) { page_spread_ = spread; } private: + base::i18n::TextDirection direction_ = base::i18n::UNKNOWN_DIRECTION; PageOrientation default_page_orientation_ = PageOrientation::kOriginal; PageSpread page_spread_ = PageSpread::kOneUp; }; diff --git a/chromium/pdf/document_layout_unittest.cc b/chromium/pdf/document_layout_unittest.cc index b10e6e353ef..3b420d108a3 100644 --- a/chromium/pdf/document_layout_unittest.cc +++ b/chromium/pdf/document_layout_unittest.cc @@ -4,6 +4,11 @@ #include "pdf/document_layout.h" +#include "base/i18n/rtl.h" +#include "base/test/values_test_util.h" +#include "base/values.h" +#include "pdf/page_orientation.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" @@ -18,34 +23,43 @@ class DocumentLayoutOptionsTest : public testing::Test { }; TEST_F(DocumentLayoutOptionsTest, DefaultConstructor) { + EXPECT_EQ(options_.direction(), base::i18n::UNKNOWN_DIRECTION); EXPECT_EQ(options_.default_page_orientation(), PageOrientation::kOriginal); EXPECT_EQ(options_.page_spread(), DocumentLayout::PageSpread::kOneUp); } TEST_F(DocumentLayoutOptionsTest, CopyConstructor) { + options_.set_direction(base::i18n::RIGHT_TO_LEFT); options_.RotatePagesClockwise(); options_.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd); DocumentLayout::Options copy(options_); + EXPECT_EQ(copy.direction(), base::i18n::RIGHT_TO_LEFT); EXPECT_EQ(copy.default_page_orientation(), PageOrientation::kClockwise90); EXPECT_EQ(copy.page_spread(), DocumentLayout::PageSpread::kTwoUpOdd); + options_.set_direction(base::i18n::LEFT_TO_RIGHT); options_.RotatePagesClockwise(); options_.set_page_spread(DocumentLayout::PageSpread::kOneUp); + EXPECT_EQ(copy.direction(), base::i18n::RIGHT_TO_LEFT); EXPECT_EQ(copy.default_page_orientation(), PageOrientation::kClockwise90); EXPECT_EQ(copy.page_spread(), DocumentLayout::PageSpread::kTwoUpOdd); } TEST_F(DocumentLayoutOptionsTest, CopyAssignment) { + options_.set_direction(base::i18n::RIGHT_TO_LEFT); options_.RotatePagesClockwise(); options_.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd); DocumentLayout::Options copy = options_; + EXPECT_EQ(copy.direction(), base::i18n::RIGHT_TO_LEFT); EXPECT_EQ(copy.default_page_orientation(), PageOrientation::kClockwise90); EXPECT_EQ(copy.page_spread(), DocumentLayout::PageSpread::kTwoUpOdd); + options_.set_direction(base::i18n::LEFT_TO_RIGHT); options_.RotatePagesClockwise(); options_.set_page_spread(DocumentLayout::PageSpread::kOneUp); + EXPECT_EQ(copy.direction(), base::i18n::RIGHT_TO_LEFT); EXPECT_EQ(copy.default_page_orientation(), PageOrientation::kClockwise90); EXPECT_EQ(copy.page_spread(), DocumentLayout::PageSpread::kTwoUpOdd); } @@ -56,6 +70,12 @@ TEST_F(DocumentLayoutOptionsTest, Equals) { DocumentLayout::Options copy; EXPECT_TRUE(copy == options_); + options_.set_direction(base::i18n::RIGHT_TO_LEFT); + EXPECT_FALSE(copy == options_); + + copy.set_direction(base::i18n::RIGHT_TO_LEFT); + EXPECT_TRUE(copy == options_); + options_.RotatePagesClockwise(); EXPECT_FALSE(copy == options_); @@ -73,12 +93,6 @@ TEST_F(DocumentLayoutOptionsTest, Equals) { copy.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd); EXPECT_TRUE(copy == options_); - - options_.set_page_spread(DocumentLayout::PageSpread::kOneUp); - EXPECT_FALSE(copy == options_); - - copy.set_page_spread(DocumentLayout::PageSpread::kOneUp); - EXPECT_TRUE(copy == options_); } TEST_F(DocumentLayoutOptionsTest, NotEquals) { @@ -96,6 +110,51 @@ TEST_F(DocumentLayoutOptionsTest, NotEquals) { EXPECT_FALSE(copy != options_); } +TEST_F(DocumentLayoutOptionsTest, ToValueDefault) { + base::Value value = options_.ToValue(); + + EXPECT_THAT(value, base::test::IsJson(R"({ + "direction": 0, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + })")); +} + +TEST_F(DocumentLayoutOptionsTest, ToValueModified) { + options_.set_direction(base::i18n::LEFT_TO_RIGHT); + options_.RotatePagesClockwise(); + options_.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd); + base::Value value = options_.ToValue(); + + EXPECT_THAT(value, base::test::IsJson(R"({ + "direction": 2, + "defaultPageOrientation": 1, + "twoUpViewEnabled": true, + })")); +} + +TEST_F(DocumentLayoutOptionsTest, FromValueDefault) { + options_.FromValue(base::test::ParseJson(R"({ + "direction": 0, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + })")); + + EXPECT_EQ(options_, DocumentLayout::Options()); +} + +TEST_F(DocumentLayoutOptionsTest, FromValueModified) { + options_.FromValue(base::test::ParseJson(R"({ + "direction": 2, + "defaultPageOrientation": 1, + "twoUpViewEnabled": true, + })")); + + EXPECT_EQ(options_.direction(), base::i18n::LEFT_TO_RIGHT); + EXPECT_EQ(options_.default_page_orientation(), PageOrientation::kClockwise90); + EXPECT_EQ(options_.page_spread(), DocumentLayout::PageSpread::kTwoUpOdd); +} + TEST_F(DocumentLayoutOptionsTest, RotatePagesClockwise) { options_.RotatePagesClockwise(); EXPECT_EQ(options_.default_page_orientation(), PageOrientation::kClockwise90); diff --git a/chromium/pdf/features.gni b/chromium/pdf/features.gni index b66bc626fa3..67ce26d6189 100644 --- a/chromium/pdf/features.gni +++ b/chromium/pdf/features.gni @@ -2,8 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//ash/webui/media_app_ui/media_app_ui.gni") import("//build/config/chromecast_build.gni") -import("//chromeos/components/media_app_ui/media_app_ui.gni") # Most build code won't need to include this file. Instead you can # unconditionally depend on "//pdf" which will be a no-op when PDF support is diff --git a/chromium/pdf/mojom/pdf.mojom b/chromium/pdf/mojom/pdf.mojom index a6cc7415d4a..413fc74dd2e 100644 --- a/chromium/pdf/mojom/pdf.mojom +++ b/chromium/pdf/mojom/pdf.mojom @@ -20,6 +20,23 @@ interface PdfListener { SetSelectionBounds(gfx.mojom.PointF base, gfx.mojom.PointF extent); }; +// Renderer-side interface to access the PDF extension. +// The browser passes the remote for this interface to the PDF renderer +// associated with the PDF extension. +interface PdfFindInPage { + // Sets the find-in-page tickmarks in the PDF extension UI. + SetTickmarks(array tickmark); +}; + +// Renderer-side interface to retrieve the PdfFindInPage remote for a specific +// RFH from a PDF extension. Only used by the browser to facilitate PDF renderer +// to PDF extension communication. +interface PdfFindInPageFactory { + // Retrieves the remote to pass to the PDF renderer. + GetPdfFindInPage() => (pending_remote find_remote); +}; + +// Browser-side interface shared by PDF plugins and PDF renderers. interface PdfService { SetListener(pending_remote client); @@ -39,4 +56,8 @@ interface PdfService { // Notifies the embedder know the plugin can handle save commands internally. SetPluginCanSave(bool can_save); + + // Retrieves the PdfFindInPage remote. Only used by PDF renderers. + [Sync] + GetPdfFindInPage() => (pending_remote find_remote); }; diff --git a/chromium/pdf/out_of_process_instance.cc b/chromium/pdf/out_of_process_instance.cc index d2a44fc43bb..a341d085c1f 100644 --- a/chromium/pdf/out_of_process_instance.cc +++ b/chromium/pdf/out_of_process_instance.cc @@ -9,18 +9,21 @@ #include #include +#include #include #include +#include #include #include "base/bind.h" #include "base/callback.h" #include "base/location.h" -#include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" +#include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/time/time.h" #include "base/values.h" @@ -29,7 +32,6 @@ #include "net/base/escape.h" #include "pdf/accessibility.h" #include "pdf/accessibility_structs.h" -#include "pdf/buildflags.h" #include "pdf/document_attachment_info.h" #include "pdf/document_metadata.h" #include "pdf/pdfium/pdfium_engine.h" @@ -71,6 +73,7 @@ #include "ui/gfx/geometry/point_f.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" +#include "url/gurl.h" #if defined(OS_LINUX) || defined(OS_CHROMEOS) #include "pdf/ppapi_migration/pdfium_font_linux.h" @@ -80,9 +83,6 @@ namespace chrome_pdf { namespace { -constexpr base::TimeDelta kFindResultCooldown = - base::TimeDelta::FromMilliseconds(100); - constexpr char kPPPPdfInterface[] = PPP_PDF_INTERFACE_1; PP_Var GetLinkAtPosition(PP_Instance instance, PP_Point point) { @@ -443,44 +443,33 @@ bool OutOfProcessInstance::Init(uint32_t argc, pp::Var document_url_var = pp::URLUtil_Dev::Get()->GetDocumentURL(this); if (!document_url_var.is_string()) return false; - - // Check if the PDF is being loaded in the PDF chrome extension. We only allow - // the plugin to be loaded in the extension and print preview to avoid - // exposing sensitive APIs directly to external websites. - // - // This is enforced before launching the plugin process (see - // ChromeContentBrowserClient::ShouldAllowPluginCreation), so below we just do - // a CHECK as a defense-in-depth. - std::string document_url = document_url_var.AsString(); - base::StringPiece document_url_piece(document_url); - set_is_print_preview(IsPrintPreviewUrl(document_url_piece)); - ValidateDocumentUrl(document_url_piece); + GURL document_url(document_url_var.AsString()); // Allow the plugin to handle find requests. SetPluginToHandleFindRequests(); text_input_ = std::make_unique(this); - PDFiumFormFiller::ScriptOption script_option = - PDFiumFormFiller::DefaultScriptOption(); - bool has_edits = false; const char* src_url = nullptr; const char* original_url = nullptr; const char* top_level_url = nullptr; + bool full_frame = false; + SkColor background_color = SK_ColorTRANSPARENT; + PDFiumFormFiller::ScriptOption script_option = + PDFiumFormFiller::DefaultScriptOption(); + bool has_edits = false; for (uint32_t i = 0; i < argc; ++i) { - if (strcmp(argn[i], "original-url") == 0) { - original_url = argv[i]; - } else if (strcmp(argn[i], "src") == 0) { + if (strcmp(argn[i], "src") == 0) { src_url = argv[i]; + } else if (strcmp(argn[i], "original-url") == 0) { + original_url = argv[i]; } else if (strcmp(argn[i], "top-level-url") == 0) { top_level_url = argv[i]; } else if (strcmp(argn[i], "full-frame") == 0) { - set_full_frame(true); + full_frame = true; } else if (strcmp(argn[i], "background-color") == 0) { - SkColor background_color; if (!base::StringToUint(argv[i], &background_color)) return false; - SetBackgroundColor(background_color); } else if (strcmp(argn[i], "javascript") == 0) { if (strcmp(argv[i], "allow") != 0) script_option = PDFiumFormFiller::ScriptOption::kNoJavaScript; @@ -495,27 +484,14 @@ bool OutOfProcessInstance::Init(uint32_t argc, if (!original_url) original_url = src_url; - InitializeEngine(std::make_unique(this, script_option)); - - // If we're in print preview mode we don't need to load the document yet. - // A `kJSResetPrintPreviewModeType` message will be sent to the plugin letting - // it know the url to load. By not loading here we avoid loading the same - // document twice. - if (IsPrintPreview()) - return true; - - LoadUrl(src_url, /*is_print_preview=*/false); - set_url(original_url); - - // Not all edits go through the PDF plugin's form filler. The plugin instance - // can be restarted by exiting annotation mode on ChromeOS, which can set the - // document to an edited state. - set_edit_mode(has_edits); -#if !BUILDFLAG(ENABLE_INK) - DCHECK(!edit_mode()); -#endif // !BUILDFLAG(ENABLE_INK) - pp::PDF::SetCrashData(this, original_url, top_level_url); + InitializeBase(std::make_unique(this, script_option), + /*embedder_origin=*/document_url.GetOrigin().spec(), + /*src_url=*/src_url, + /*original_url=*/original_url, + /*full_frame=*/full_frame, + /*background_color=*/background_color, + /*has_edits=*/has_edits); return true; } @@ -532,16 +508,9 @@ bool OutOfProcessInstance::HandleInputEvent(const pp::InputEvent& event) { } void OutOfProcessInstance::DidChangeView(const pp::View& view) { - UpdateGeometryOnViewChanged(RectFromPPRect(view.GetRect()), - view.GetDeviceScale()); - - if (IsPrintPreview() && !stop_scrolling()) { - set_scroll_position(PointFromPPPoint(view.GetScrollOffset())); - UpdateScroll(); - } - - // Scrolling in the main PDF Viewer UI is already handled by - // HandleUpdateScrollMessage(). + const gfx::Rect new_plugin_rect = gfx::ScaleToEnclosingRectSafe( + RectFromPPRect(view.GetRect()), view.GetDeviceScale()); + UpdateGeometryOnPluginRectChanged(new_plugin_rect, view.GetDeviceScale()); } void OutOfProcessInstance::DidChangeFocus(bool has_focus) { @@ -670,30 +639,15 @@ bool OutOfProcessInstance::IsPrintScalingDisabled() { bool OutOfProcessInstance::StartFind(const std::string& text, bool case_sensitive) { - engine()->StartFind(text, case_sensitive); - return true; + return PdfViewPluginBase::StartFind(text, case_sensitive); } void OutOfProcessInstance::SelectFindResult(bool forward) { - engine()->SelectFindResult(forward); + PdfViewPluginBase::SelectFindResult(forward); } void OutOfProcessInstance::StopFind() { - engine()->StopFind(); - tickmarks_.clear(); - SetTickmarks(tickmarks_); -} - -void OutOfProcessInstance::DidOpen(std::unique_ptr loader, - int32_t result) { - if (result == PP_OK) { - if (!engine()->HandleDocumentLoad(std::move(loader), GetURL())) { - set_document_load_state(DocumentLoadState::kLoading); - DocumentLoadFailed(); - } - } else if (result != PP_ERROR_ABORTED) { // Can happen in tests. - DocumentLoadFailed(); - } + PdfViewPluginBase::StopFind(); } void OutOfProcessInstance::SendMessage(base::Value message) { @@ -735,41 +689,6 @@ void OutOfProcessInstance::UpdateCursor(ui::mojom::CursorType new_cursor_type) { pp::ImageData().pp_resource(), nullptr); } -void OutOfProcessInstance::UpdateTickMarks( - const std::vector& tickmarks) { - float inverse_scale = 1.0f / device_scale(); - tickmarks_.clear(); - tickmarks_.reserve(tickmarks.size()); - for (auto& tickmark : tickmarks) { - tickmarks_.emplace_back( - PPRectFromRect(gfx::ScaleToEnclosingRect(tickmark, inverse_scale))); - } -} - -void OutOfProcessInstance::NotifyNumberOfFindResultsChanged(int total, - bool final_result) { - // We don't want to spam the renderer with too many updates to the number of - // find results. Don't send an update if we sent one too recently. If it's the - // final update, we always send it though. - if (final_result) { - NumberOfFindResultsChanged(total, final_result); - SetTickmarks(tickmarks_); - return; - } - - if (recently_sent_find_update_) - return; - - NumberOfFindResultsChanged(total, final_result); - SetTickmarks(tickmarks_); - recently_sent_find_update_ = true; - ScheduleTaskOnMainThread( - FROM_HERE, - base::BindOnce(&OutOfProcessInstance::ResetRecentlySentFindUpdate, - weak_factory_.GetWeakPtr()), - /*result=*/0, kFindResultCooldown); -} - void OutOfProcessInstance::NotifySelectedFindResultChanged( int current_find_index) { DCHECK_GE(current_find_index, -1); @@ -797,24 +716,6 @@ std::string OutOfProcessInstance::Prompt(const std::string& question, return result.is_string() ? result.AsString() : std::string(); } -void OutOfProcessInstance::SubmitForm(const std::string& url, - const void* data, - int length) { - UrlRequest request; - request.url = url; - request.method = "POST"; - request.body.assign(static_cast(data), length); - - form_loader_ = CreateUrlLoaderInternal(); - form_loader_->Open(request, base::BindOnce(&OutOfProcessInstance::FormDidOpen, - weak_factory_.GetWeakPtr())); -} - -void OutOfProcessInstance::FormDidOpen(int32_t result) { - // TODO(crbug.com/719344): Process response. - LOG_IF(ERROR, result != PP_OK) << "FormDidOpen failed: " << result; -} - std::vector OutOfProcessInstance::SearchString(const char16_t* string, const char16_t* term, @@ -851,10 +752,6 @@ void OutOfProcessInstance::SetLastPluginInstance() { #endif } -void OutOfProcessInstance::ResetRecentlySentFindUpdate(int32_t /* unused */) { - recently_sent_find_update_ = false; -} - Image OutOfProcessInstance::GetPluginImageData() const { return Image(pepper_image_data_); } @@ -902,6 +799,20 @@ void OutOfProcessInstance::SetAccessibilityViewportInfo( pp::PDF::SetAccessibilityViewportInfo(this, &pp_viewport_info); } +void OutOfProcessInstance::NotifyFindResultsChanged(int total, + bool final_result) { + NumberOfFindResultsChanged(total, final_result); +} + +void OutOfProcessInstance::NotifyFindTickmarks( + const std::vector& tickmarks) { + std::vector pp_tickmarks; + pp_tickmarks.reserve(tickmarks.size()); + std::transform(tickmarks.begin(), tickmarks.end(), + std::back_inserter(pp_tickmarks), PPRectFromRect); + SetTickmarks(pp_tickmarks); +} + void OutOfProcessInstance::SetPluginCanSave(bool can_save) { pp::PDF::SetPluginCanSave(this, can_save); } @@ -916,6 +827,29 @@ std::unique_ptr OutOfProcessInstance::CreateUrlLoaderInternal() { return loader; } +std::string OutOfProcessInstance::RewriteRequestUrl( + base::StringPiece url) const { + if (IsPrintPreview()) { + // TODO(crbug.com/1238829): This is a workaround for Pepper not supporting + // chrome-untrusted://print/ URLs. Pepper issues requests through the + // embedder's URL loaders, but a WebUI loader only supports subresource + // requests to the same scheme (so chrome: only can request chrome: URLs, + // and chrome-untrusted: only can request chrome-untrusted: URLs). + // + // To work around this (for the Pepper plugin only), we'll issue + // chrome-untrusted://print/ requests to the equivalent chrome://print/ URL, + // since both schemes support the same PDF URLs. + if (base::StartsWith(url, kChromeUntrustedPrintHost)) { + return base::StrCat( + {kChromePrintHost, url.substr(kChromeUntrustedPrintHost.size())}); + } + + NOTREACHED(); + } + + return PdfViewPluginBase::RewriteRequestUrl(url); +} + void OutOfProcessInstance::SetSelectedText(const std::string& selected_text) { pp::PDF::SetSelectedText(this, selected_text.c_str()); } diff --git a/chromium/pdf/out_of_process_instance.h b/chromium/pdf/out_of_process_instance.h index f0f9f075af7..d9ed221b3c2 100644 --- a/chromium/pdf/out_of_process_instance.h +++ b/chromium/pdf/out_of_process_instance.h @@ -89,17 +89,12 @@ class OutOfProcessInstance : public PdfViewPluginBase, // PdfViewPluginBase: void UpdateCursor(ui::mojom::CursorType new_cursor_type) override; - void UpdateTickMarks(const std::vector& tickmarks) override; - void NotifyNumberOfFindResultsChanged(int total, bool final_result) override; void NotifySelectedFindResultChanged(int current_find_index) override; void CaretChanged(const gfx::Rect& caret_rect) override; void Alert(const std::string& message) override; bool Confirm(const std::string& message) override; std::string Prompt(const std::string& question, const std::string& default_answer) override; - void SubmitForm(const std::string& url, - const void* data, - int length) override; std::vector SearchString(const char16_t* string, const char16_t* term, bool case_sensitive) override; @@ -121,7 +116,7 @@ class OutOfProcessInstance : public PdfViewPluginBase, // PdfViewPluginBase: base::WeakPtr GetWeakPtr() override; std::unique_ptr CreateUrlLoaderInternal() override; - void DidOpen(std::unique_ptr loader, int32_t result) override; + std::string RewriteRequestUrl(base::StringPiece url) const override; void SendMessage(base::Value message) override; void SaveAs() override; void InitImageData(const gfx::Size& size) override; @@ -134,6 +129,8 @@ class OutOfProcessInstance : public PdfViewPluginBase, AccessibilityPageObjects page_objects) override; void SetAccessibilityViewportInfo( const AccessibilityViewportInfo& viewport_info) override; + void NotifyFindResultsChanged(int total, bool final_result) override; + void NotifyFindTickmarks(const std::vector& tickmarks) override; void SetContentRestrictions(int content_restrictions) override; void SetPluginCanSave(bool can_save) override; void PluginDidStartLoading() override; @@ -148,12 +145,8 @@ class OutOfProcessInstance : public PdfViewPluginBase, void UserMetricsRecordAction(const std::string& action) override; private: - void ResetRecentlySentFindUpdate(int32_t); - bool CanSaveEdits() const; - void FormDidOpen(int32_t result); - // The Pepper image data that is in sync with mutable_image_data(). pp::ImageData pepper_image_data_; @@ -165,13 +158,6 @@ class OutOfProcessInstance : public PdfViewPluginBase, // http://crbug.com/132565 std::unique_ptr text_input_; - // Whether an update to the number of find results found was sent less than - // `kFindResultCooldownMs` milliseconds ago. - bool recently_sent_find_update_ = false; - - // The tickmarks. - std::vector tickmarks_; - base::WeakPtrFactory weak_factory_{this}; }; diff --git a/chromium/pdf/pdf.h b/chromium/pdf/pdf.h index 83e0be37ee6..66090764d1a 100644 --- a/chromium/pdf/pdf.h +++ b/chromium/pdf/pdf.h @@ -48,6 +48,9 @@ enum PrintingMode { // Values 4 and 5 are similar to `kPostScript2` and `kPostScript3`, but are // not intended for use in sandboxed environments like Chromium's. kEmfWithReducedRasterization = 6, + kPostScript3WithType42Fonts = 7, + // Value 8 is similar to `kPostScript3WithType42Fonts`, but is not intended + // for use in sandboxed environments like Chromium's. }; // `pdf_buffer` is the buffer that contains the entire PDF document to be diff --git a/chromium/pdf/pdf_accessibility_action_handler.h b/chromium/pdf/pdf_accessibility_action_handler.h index f065fffbb00..53e936148f8 100644 --- a/chromium/pdf/pdf_accessibility_action_handler.h +++ b/chromium/pdf/pdf_accessibility_action_handler.h @@ -13,6 +13,7 @@ class PdfAccessibilityActionHandler { public: virtual ~PdfAccessibilityActionHandler() = default; + virtual void EnableAccessibility() = 0; virtual void HandleAccessibilityAction( const AccessibilityActionData& action_data) = 0; }; diff --git a/chromium/pdf/pdf_utils/dates.cc b/chromium/pdf/pdf_utils/dates.cc index 6d2f9ba3343..51265b06a20 100644 --- a/chromium/pdf/pdf_utils/dates.cc +++ b/chromium/pdf/pdf_utils/dates.cc @@ -80,7 +80,7 @@ base::TimeDelta ParseOffset(DateDeserializer& deserializer) { if (!sign.has_value() || (sign.value() != '+' && sign.value() != '-')) return offset; - offset += base::TimeDelta::FromHours(deserializer.PopDigits(2).value_or(0)); + offset += base::Hours(deserializer.PopDigits(2).value_or(0)); // The spec requires that the hours offset be followed by an apostrophe, but // don't be strict about its presence. @@ -92,7 +92,7 @@ base::TimeDelta ParseOffset(DateDeserializer& deserializer) { // following the minutes offset. One reason for the leniency is the apostrophe // following the minues, which is only mentioned in earlier versions of the // spec. - offset += base::TimeDelta::FromMinutes(deserializer.PopDigits(2).value_or(0)); + offset += base::Minutes(deserializer.PopDigits(2).value_or(0)); return sign.value() == '+' ? offset : -offset; } diff --git a/chromium/pdf/pdf_view_plugin_base.cc b/chromium/pdf/pdf_view_plugin_base.cc index e795ae37e69..090ee1979ef 100644 --- a/chromium/pdf/pdf_view_plugin_base.cc +++ b/chromium/pdf/pdf_view_plugin_base.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -22,8 +23,10 @@ #include "base/containers/span.h" #include "base/cxx17_backports.h" #include "base/feature_list.h" +#include "base/i18n/rtl.h" #include "base/i18n/time_formatting.h" #include "base/location.h" +#include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/metrics/histogram_functions.h" #include "base/notreached.h" @@ -43,8 +46,10 @@ #include "pdf/document_layout.h" #include "pdf/document_metadata.h" #include "pdf/paint_ready_rect.h" +#include "pdf/pdf_engine.h" #include "pdf/pdf_features.h" #include "pdf/pdfium/pdfium_engine.h" +#include "pdf/pdfium/pdfium_form_filler.h" #include "pdf/ppapi_migration/image.h" #include "pdf/ppapi_migration/result_codes.h" #include "pdf/ppapi_migration/url_loader.h" @@ -60,12 +65,14 @@ #include "third_party/skia/include/core/SkColor.h" #include "ui/base/text/bytes_formatting.h" #include "ui/events/blink/blink_event_util.h" +#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point_f.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/skia_conversions.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/geometry/vector2d_f.h" -#include "ui/gfx/skia_util.h" +#include "url/gurl.h" namespace chrome_pdf { @@ -76,12 +83,12 @@ constexpr double kMinZoom = 0.01; // A delay to wait between each accessibility page to keep the system // responsive. -constexpr base::TimeDelta kAccessibilityPageDelay = - base::TimeDelta::FromMilliseconds(100); +constexpr base::TimeDelta kAccessibilityPageDelay = base::Milliseconds(100); + +constexpr base::TimeDelta kFindResultCooldown = base::Milliseconds(100); -constexpr char kChromePrintHost[] = "chrome://print/"; constexpr char kChromeExtensionHost[] = - "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai"; + "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/"; // Same value as printing::COMPLETE_PREVIEW_DOCUMENT_INDEX. constexpr int kCompletePDFIndex = -1; @@ -112,12 +119,16 @@ base::Value PrepareReplyMessage(base::StringPiece reply_type, return reply; } +bool IsPrintPreviewUrl(base::StringPiece url) { + return base::StartsWith(url, PdfViewPluginBase::kChromeUntrustedPrintHost); +} + int ExtractPrintPreviewPageIndex(base::StringPiece src_url) { - // Sample `src_url` format: chrome://print/id/page_index/print.pdf + // Sample `src_url` format: chrome-untrusted://print/id/page_index/print.pdf // The page_index is zero-based, but can be negative with special meanings. - std::vector url_substr = - base::SplitStringPiece(src_url.substr(strlen(kChromePrintHost)), "/", - base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + std::vector url_substr = base::SplitStringPiece( + src_url.substr(PdfViewPluginBase::kChromeUntrustedPrintHost.size()), "/", + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); if (url_substr.size() != 3) return kInvalidPDFIndex; @@ -136,10 +147,58 @@ bool IsPreviewingPDF(int print_preview_page_count) { } // namespace +// static +constexpr base::StringPiece PdfViewPluginBase::kChromePrintHost; + +// static +constexpr base::StringPiece PdfViewPluginBase::kChromeUntrustedPrintHost; + PdfViewPluginBase::PdfViewPluginBase() = default; PdfViewPluginBase::~PdfViewPluginBase() = default; +void PdfViewPluginBase::InitializeBase(std::unique_ptr engine, + base::StringPiece embedder_origin, + base::StringPiece src_url, + base::StringPiece original_url, + bool full_frame, + SkColor background_color, + bool has_edits) { + // Check if the PDF is being loaded in the PDF chrome extension. We only allow + // the plugin to be loaded in the extension and print preview to avoid + // exposing sensitive APIs directly to external websites. + // + // This is enforced before launching the plugin process (see + // ChromeContentBrowserClient::ShouldAllowPluginCreation), so below we just do + // a CHECK as a defense-in-depth. + is_print_preview_ = (embedder_origin == kChromePrintHost); + CHECK(IsPrintPreview() || embedder_origin == kChromeExtensionHost); + + full_frame_ = full_frame; + background_color_ = background_color; + + DCHECK(engine); + engine_ = std::move(engine); + + // If we're in print preview mode we don't need to load the document yet. + // A `kJSResetPrintPreviewModeType` message will be sent to the plugin letting + // it know the url to load. By not loading here we avoid loading the same + // document twice. + if (IsPrintPreview()) + return; + + LoadUrl(src_url, /*is_print_preview=*/false); + url_ = std::string(original_url); + + // Not all edits go through the PDF plugin's form filler. The plugin instance + // can be restarted by exiting annotation mode on ChromeOS, which can set the + // document to an edited state. + edit_mode_ = has_edits; +#if !BUILDFLAG(ENABLE_INK) + DCHECK(!edit_mode_); +#endif // !BUILDFLAG(ENABLE_INK) +} + void PdfViewPluginBase::ProposeDocumentLayout(const DocumentLayout& layout) { base::Value message(base::Value::Type::DICTIONARY); message.SetStringKey("type", "documentDimensions"); @@ -147,15 +206,8 @@ void PdfViewPluginBase::ProposeDocumentLayout(const DocumentLayout& layout) { message.SetIntKey("height", layout.size().height()); message.SetKey("layoutOptions", layout.options().ToValue()); base::Value page_dimensions_list(base::Value::Type::LIST); - for (size_t i = 0; i < layout.page_count(); ++i) { - const gfx::Rect& page_rect = layout.page_rect(i); - base::Value page_dimensions(base::Value::Type::DICTIONARY); - page_dimensions.SetIntKey("x", page_rect.x()); - page_dimensions.SetIntKey("y", page_rect.y()); - page_dimensions.SetIntKey("width", page_rect.width()); - page_dimensions.SetIntKey("height", page_rect.height()); - page_dimensions_list.Append(std::move(page_dimensions)); - } + for (size_t i = 0; i < layout.page_count(); ++i) + page_dimensions_list.Append(base::Value(DictFromRect(layout.page_rect(i)))); message.SetKey("pageDimensions", std::move(page_dimensions_list)); SendMessage(std::move(message)); @@ -244,6 +296,40 @@ void PdfViewPluginBase::NavigateToDestination(int page, SendMessage(std::move(message)); } +void PdfViewPluginBase::UpdateTickMarks( + const std::vector& tickmarks) { + float inverse_scale = 1.0f / device_scale_; + tickmarks_.clear(); + tickmarks_.reserve(tickmarks.size()); + std::transform(tickmarks.begin(), tickmarks.end(), + std::back_inserter(tickmarks_), + [inverse_scale](const gfx::Rect& t) -> gfx::Rect { + return gfx::ScaleToEnclosingRect(t, inverse_scale); + }); +} + +void PdfViewPluginBase::NotifyNumberOfFindResultsChanged(int total, + bool final_result) { + // We don't want to spam the renderer with too many updates to the number of + // find results. Don't send an update if we sent one too recently. If it's the + // final update, we always send it though. + if (recently_sent_find_update_ && !final_result) + return; + + NotifyFindResultsChanged(total, final_result); + NotifyFindTickmarks(tickmarks_); + + if (final_result) + return; + + recently_sent_find_update_ = true; + ScheduleTaskOnMainThread( + FROM_HERE, + base::BindOnce(&PdfViewPluginBase::ResetRecentlySentFindUpdate, + GetWeakPtr()), + /*result=*/0, kFindResultCooldown); +} + void PdfViewPluginBase::NotifyTouchSelectionOccurred() { base::Value message(base::Value::Type::DICTIONARY); message.SetStringKey("type", "touchSelectionOccurred"); @@ -298,6 +384,24 @@ void PdfViewPluginBase::Print() { InvokePrintDialog(); } +void PdfViewPluginBase::SubmitForm(const std::string& url, + const void* data, + int length) { + // `url` might be a relative URL. Resolve it against the document's URL. + GURL resolved_url = GURL(GetURL()).Resolve(url); + if (!resolved_url.is_valid()) + return; + + UrlRequest request; + request.url = resolved_url.spec(); + request.method = "POST"; + request.body.assign(static_cast(data), length); + + form_loader_ = CreateUrlLoaderInternal(); + form_loader_->Open( + request, base::BindOnce(&PdfViewPluginBase::DidFormOpen, GetWeakPtr())); +} + std::unique_ptr PdfViewPluginBase::CreateUrlLoader() { if (full_frame_) { DidStartLoading(); @@ -327,7 +431,6 @@ void PdfViewPluginBase::DocumentLoadComplete() { SendAttachments(); SendBookmarks(); SendMetadata(); - SendLoadingProgress(/*percentage=*/100); if (accessibility_state_ == AccessibilityState::kPending) LoadAccessibility(); @@ -390,7 +493,6 @@ void PdfViewPluginBase::DocumentLoadProgress(uint32_t available, if (progress <= last_progress_sent_ + 1) return; - last_progress_sent_ = progress; SendLoadingProgress(progress); } @@ -568,6 +670,7 @@ void PdfViewPluginBase::ConsumeSaveToken(const std::string& token) { void PdfViewPluginBase::SendLoadingProgress(double percentage) { DCHECK(percentage == -1 || (percentage >= 0 && percentage <= 100)); + last_progress_sent_ = percentage; base::Value message(base::Value::Type::DICTIONARY); message.SetStringKey("type", "loadProgress"); @@ -663,11 +766,18 @@ bool PdfViewPluginBase::UnsupportedFeatureIsReportedForTesting( return base::Contains(unsupported_features_reported_, feature); } -void PdfViewPluginBase::InitializeEngine(std::unique_ptr engine) { +void PdfViewPluginBase::InitializeEngineForTesting( + std::unique_ptr engine) { DCHECK(engine); engine_ = std::move(engine); } +std::unique_ptr PdfViewPluginBase::CreateEngine( + PDFEngine::Client* client, + PDFiumFormFiller::ScriptOption script_option) { + return std::make_unique(client, script_option); +} + void PdfViewPluginBase::DestroyEngine() { engine_.reset(); } @@ -676,14 +786,13 @@ void PdfViewPluginBase::DestroyPreviewEngine() { preview_engine_.reset(); } -void PdfViewPluginBase::ValidateDocumentUrl(base::StringPiece document_url) { - CHECK(base::StartsWith(document_url, kChromeExtensionHost) || - IsPrintPreview()); -} +void PdfViewPluginBase::LoadUrl(base::StringPiece url, bool is_print_preview) { + // `last_progress_sent_` should only be reset for the primary load. + if (!is_print_preview) + last_progress_sent_ = 0; -void PdfViewPluginBase::LoadUrl(const std::string& url, bool is_print_preview) { UrlRequest request; - request.url = url; + request.url = RewriteRequestUrl(url); request.method = "GET"; request.ignore_redirects = true; @@ -696,6 +805,10 @@ void PdfViewPluginBase::LoadUrl(const std::string& url, bool is_print_preview) { GetWeakPtr(), std::move(loader))); } +std::string PdfViewPluginBase::RewriteRequestUrl(base::StringPiece url) const { + return std::string(url); +} + void PdfViewPluginBase::InvalidateAfterPaintDone() { if (deferred_invalidates_.empty()) return; @@ -758,12 +871,10 @@ void PdfViewPluginBase::PrintEnd() { engine_->PrintEnd(); } -void PdfViewPluginBase::UpdateGeometryOnViewChanged( - const gfx::Rect& new_view_rect, +void PdfViewPluginBase::UpdateGeometryOnPluginRectChanged( + const gfx::Rect& new_plugin_rect, float new_device_scale) { DCHECK_GT(new_device_scale, 0.0f); - const gfx::Rect new_plugin_rect = - gfx::ScaleToEnclosingRectSafe(new_view_rect, new_device_scale); if (new_device_scale == device_scale_ && new_plugin_rect == plugin_rect_) return; @@ -771,7 +882,13 @@ void PdfViewPluginBase::UpdateGeometryOnViewChanged( const float old_device_scale = device_scale_; device_scale_ = new_device_scale; plugin_rect_ = new_plugin_rect; - plugin_dip_size_ = new_view_rect.size(); + // TODO(crbug.com/1250173): For the Pepper-free plugin, `plugin_dip_size_` is + // calculated from the `window_rect` in PdfViewWebPlugin::UpdateGeometry(). + // We should try to avoid the downscaling during this calculation process and + // maybe migrate off `plugin_dip_size_`. + plugin_dip_size_ = + gfx::ScaleToEnclosingRectSafe(new_plugin_rect, 1.0f / new_device_scale) + .size(); paint_manager_.SetSize(plugin_rect_.size(), device_scale_); @@ -850,26 +967,31 @@ void PdfViewPluginBase::CalculateBackgroundParts() { background_parts_.push_back(part); } -void PdfViewPluginBase::UpdateScroll() { - DCHECK(!stop_scrolling_); - const gfx::PointF scaled_scroll_position = gfx::ScalePoint( - BoundScrollPositionToDocument(gfx::PointF(scroll_position_)), - device_scale_); - engine()->ScrolledToXPosition(scaled_scroll_position.x()); - engine()->ScrolledToYPosition(scaled_scroll_position.y()); -} +void PdfViewPluginBase::UpdateScroll(const gfx::Vector2dF& scroll_offset) { + if (stop_scrolling_) + return; -gfx::PointF PdfViewPluginBase::BoundScrollPositionToDocument( - const gfx::PointF& scroll_position) { float max_x = std::max(document_size_.width() * static_cast(zoom_) - plugin_dip_size_.width(), 0.0f); - float x = base::clamp(scroll_position.x(), 0.0f, max_x); float max_y = std::max(document_size_.height() * static_cast(zoom_) - plugin_dip_size_.height(), 0.0f); - float y = base::clamp(scroll_position.y(), 0.0f, max_y); - return gfx::PointF(x, y); + + // TODO(crbug.com/1256965): Right-to-left scrolling currently is not + // compatible with the PDF viewer's "scroller" element. + gfx::PointF scroll_position; + if (ui_direction_ == base::i18n::RIGHT_TO_LEFT && IsPrintPreview()) + scroll_position.set_x(max_x); + scroll_position += scroll_offset; + + gfx::PointF scaled_scroll_position( + base::clamp(scroll_position.x(), 0.0f, max_x), + base::clamp(scroll_position.y(), 0.0f, max_y)); + scaled_scroll_position.Scale(device_scale_); + + engine()->ScrolledToXPosition(scaled_scroll_position.x()); + engine()->ScrolledToYPosition(scaled_scroll_position.y()); } int PdfViewPluginBase::GetDocumentPixelWidth() const { @@ -926,6 +1048,22 @@ void PdfViewPluginBase::PrepareAndSetAccessibilityViewportInfo() { SetAccessibilityViewportInfo(viewport_info); } +bool PdfViewPluginBase::StartFind(const std::string& text, + bool case_sensitive) { + engine_->StartFind(text, case_sensitive); + return true; +} + +void PdfViewPluginBase::SelectFindResult(bool forward) { + engine_->SelectFindResult(forward); +} + +void PdfViewPluginBase::StopFind() { + engine_->StopFind(); + tickmarks_.clear(); + NotifyFindTickmarks(tickmarks_); +} + void PdfViewPluginBase::SetZoom(double scale) { double old_zoom = zoom_; zoom_ = scale; @@ -933,8 +1071,14 @@ void PdfViewPluginBase::SetZoom(double scale) { } // static -bool PdfViewPluginBase::IsPrintPreviewUrl(base::StringPiece url) { - return base::StartsWith(url, kChromePrintHost); +base::Value::DictStorage PdfViewPluginBase::DictFromRect( + const gfx::Rect& rect) { + base::Value::DictStorage dict; + dict["x"] = base::Value(rect.x()); + dict["y"] = base::Value(rect.y()); + dict["width"] = base::Value(rect.width()); + dict["height"] = base::Value(rect.height()); + return dict; } void PdfViewPluginBase::HandleDisplayAnnotationsMessage( @@ -1041,8 +1185,13 @@ void PdfViewPluginBase::HandleResetPrintPreviewModeMessage( document_load_state_ = DocumentLoadState::kLoading; LoadUrl(GetURL(), /*is_print_preview=*/false); preview_engine_.reset(); - InitializeEngine(std::make_unique( - this, PDFiumFormFiller::ScriptOption::kNoJavaScript)); + + // TODO(crbug.com/1237952): Figure out a more consistent way to preserve + // engine settings across a Print Preview reset. + engine_ = CreateEngine(this, PDFiumFormFiller::ScriptOption::kNoJavaScript); + engine()->ZoomUpdated(zoom_ * device_scale_); + engine()->PageOffsetUpdated(available_area_.OffsetFromOrigin()); + engine()->PluginSizeUpdated(available_area_.size()); engine()->SetGrayscale(is_grayscale); paint_manager_.InvalidateRect(gfx::Rect(plugin_rect().size())); @@ -1112,9 +1261,8 @@ void PdfViewPluginBase::HandleSelectAllMessage(const base::Value& /*message*/) { void PdfViewPluginBase::HandleSetBackgroundColorMessage( const base::Value& message) { - const SkColor background_color = + background_color_ = base::checked_cast(message.FindDoubleKey("color").value()); - SetBackgroundColor(background_color); } void PdfViewPluginBase::HandleSetReadOnlyMessage(const base::Value& message) { @@ -1131,13 +1279,8 @@ void PdfViewPluginBase::HandleStopScrollingMessage( } void PdfViewPluginBase::HandleUpdateScrollMessage(const base::Value& message) { - if (stop_scrolling_) - return; - - scroll_position_ = - gfx::Point(base::checked_cast(message.FindDoubleKey("x").value()), - base::checked_cast(message.FindDoubleKey("y").value())); - UpdateScroll(); + UpdateScroll(gfx::Vector2dF(message.FindDoubleKey("x").value(), + message.FindDoubleKey("y").value())); } void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { @@ -1146,13 +1289,22 @@ void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { if (layout_options_value) { DocumentLayout::Options layout_options; layout_options.FromValue(*layout_options_value); + + ui_direction_ = layout_options.direction(); + // TODO(crbug.com/1013800): Eliminate need to get document size from here. document_size_ = engine()->ApplyDocumentLayout(layout_options); OnGeometryChanged(zoom_, device_scale_); + + // Send 100% loading progress only after initial layout negotiated. + if (last_progress_sent_ < 100 && + document_load_state_ == DocumentLoadState::kComplete) { + SendLoadingProgress(/*percentage=*/100); + } } - gfx::PointF scroll_position(message.FindDoubleKey("xOffset").value(), - message.FindDoubleKey("yOffset").value()); + gfx::Vector2dF scroll_offset(message.FindDoubleKey("xOffset").value(), + message.FindDoubleKey("yOffset").value()); double new_zoom = message.FindDoubleKey("zoom").value(); const PinchPhase pinch_phase = static_cast(message.FindIntKey("pinchPhase").value()); @@ -1162,7 +1314,7 @@ void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { const double zoom_ratio = new_zoom / zoom_; if (pinch_phase == PinchPhase::kStart) { - scroll_position_at_last_raster_ = scroll_position; + scroll_offset_at_last_raster_ = scroll_offset; last_bitmap_smaller_ = false; needs_reraster_ = false; return; @@ -1198,9 +1350,9 @@ void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { // We want to keep the paint in the middle but it must stay in the same // position relative to the scroll bars. paint_offset = gfx::Vector2d(0, (1 - zoom_ratio) * pinch_center.y()); - scroll_delta = - gfx::Vector2d(0, (scroll_position.y() - - scroll_position_at_last_raster_.y() * zoom_ratio)); + scroll_delta = gfx::Vector2d( + 0, + (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio)); pinch_vector = gfx::Vector2d(); last_bitmap_smaller_ = true; @@ -1215,11 +1367,9 @@ void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { (1 - new_zoom / zoom_when_doc_covers_plugin_width) * pinch_center.x(), (1 - zoom_ratio) * pinch_center.y()); pinch_vector = gfx::Vector2d(); - scroll_delta = - gfx::Vector2d((scroll_position.x() - - scroll_position_at_last_raster_.x() * zoom_ratio), - (scroll_position.y() - - scroll_position_at_last_raster_.y() * zoom_ratio)); + scroll_delta = gfx::Vector2d( + (scroll_offset.x() - scroll_offset_at_last_raster_.x() * zoom_ratio), + (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio)); } paint_manager_.SetTransform(zoom_ratio, pinch_center, @@ -1240,9 +1390,9 @@ void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { needs_reraster_ = true; // If we're rerastering due to zooming out, we need to update the scroll - // position for the last raster, in case the user continues the gesture by + // offset for the last raster, in case the user continues the gesture by // zooming in. - scroll_position_at_last_raster_ = scroll_position; + scroll_offset_at_last_raster_ = scroll_offset; } // Bound the input parameters. @@ -1250,9 +1400,7 @@ void PdfViewPluginBase::HandleViewportMessage(const base::Value& message) { DCHECK(message.FindBoolKey("userInitiated").has_value()); SetZoom(new_zoom); - scroll_position = BoundScrollPositionToDocument(scroll_position); - engine()->ScrolledToXPosition(scroll_position.x() * device_scale_); - engine()->ScrolledToYPosition(scroll_position.y() * device_scale_); + UpdateScroll(scroll_offset); } void PdfViewPluginBase::DidStartLoading() { @@ -1496,6 +1644,11 @@ void PdfViewPluginBase::LoadAccessibility() { /*result=*/0, kAccessibilityPageDelay); } +void PdfViewPluginBase::ResetRecentlySentFindUpdate( + int32_t /*unused_but_required*/) { + recently_sent_find_update_ = false; +} + namespace { // These values are persisted to logs. Entries should not be renumbered and @@ -1547,16 +1700,34 @@ void PdfViewPluginBase::HistogramCustomCounts(const char* name, base::UmaHistogramCustomCounts(name, sample, min, max, bucket_count); } +void PdfViewPluginBase::DidOpen(std::unique_ptr loader, + int32_t result) { + if (result == kSuccess) { + if (!engine()->HandleDocumentLoad(std::move(loader), GetURL())) { + document_load_state_ = DocumentLoadState::kLoading; + DocumentLoadFailed(); + } + } else if (result != kErrorAborted) { + DocumentLoadFailed(); + } +} + void PdfViewPluginBase::DidOpenPreview(std::unique_ptr loader, int32_t result) { - DCHECK_EQ(result, Result::kSuccess); + DCHECK_EQ(result, kSuccess); preview_client_ = std::make_unique(this); - preview_engine_ = std::make_unique( - preview_client_.get(), PDFiumFormFiller::ScriptOption::kNoJavaScript); + preview_engine_ = CreateEngine(preview_client_.get(), + PDFiumFormFiller::ScriptOption::kNoJavaScript); preview_engine_->PluginSizeUpdated({}); preview_engine_->HandleDocumentLoad(std::move(loader), GetURL()); } +void PdfViewPluginBase::DidFormOpen(int32_t result) { + // TODO(crbug.com/719344): Process response. + LOG_IF(ERROR, result != kSuccess) << "DidFormOpen failed: " << result; + form_loader_.reset(); +} + void PdfViewPluginBase::OnPrintPreviewLoaded() { // Scroll location is retained across document loads in print preview mode, so // there's no need to override the scroll position by scrolling again. @@ -1596,7 +1767,7 @@ void PdfViewPluginBase::ProcessPreviewPageInfo(const std::string& url, void PdfViewPluginBase::LoadAvailablePreviewPage() { if (preview_pages_info_.empty() || - document_load_state() != DocumentLoadState::kComplete || + document_load_state_ != DocumentLoadState::kComplete || preview_document_load_state_ == DocumentLoadState::kLoading) { return; } diff --git a/chromium/pdf/pdf_view_plugin_base.h b/chromium/pdf/pdf_view_plugin_base.h index b2e1662cb1c..6e3cebde7e9 100644 --- a/chromium/pdf/pdf_view_plugin_base.h +++ b/chromium/pdf/pdf_view_plugin_base.h @@ -15,7 +15,10 @@ #include "base/callback.h" #include "base/containers/flat_set.h" #include "base/containers/queue.h" +#include "base/i18n/rtl.h" #include "base/memory/weak_ptr.h" +#include "base/strings/string_piece.h" +#include "base/values.h" #include "pdf/accessibility_structs.h" #include "pdf/paint_manager.h" #include "pdf/pdf_engine.h" @@ -26,19 +29,18 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" -#include "ui/gfx/geometry/point.h" -#include "ui/gfx/geometry/point_f.h" #include "ui/gfx/geometry/rect.h" - -namespace base { -class Value; -} // namespace base +#include "ui/gfx/geometry/vector2d_f.h" namespace blink { class WebInputEvent; struct WebPrintPresetOptions; } // namespace blink +namespace gfx { +class PointF; +} // namespace gfx + namespace chrome_pdf { class Image; @@ -65,6 +67,13 @@ class PdfViewPluginBase : public PDFEngine::Client, // and is also enforced in chrome/browser/resources/pdf/pdf_viewer.js. static constexpr size_t kMaximumSavedFileSize = 100 * 1000 * 1000; + // Print Preview base URL. + static constexpr base::StringPiece kChromePrintHost = "chrome://print/"; + + // Untrusted Print Preview base URL. + static constexpr base::StringPiece kChromeUntrustedPrintHost = + "chrome-untrusted://print/"; + enum class AccessibilityState { kOff = 0, // Off. kPending, // Enabled but waiting for doc to load. @@ -101,6 +110,8 @@ class PdfViewPluginBase : public PDFEngine::Client, const float* x, const float* y, const float* zoom) override; + void UpdateTickMarks(const std::vector& tickmarks) override; + void NotifyNumberOfFindResultsChanged(int total, bool final_result) override; void NotifyTouchSelectionOccurred() override; void GetDocumentPassword( base::OnceCallback callback) override; @@ -112,6 +123,9 @@ class PdfViewPluginBase : public PDFEngine::Client, const std::string& subject, const std::string& body) override; void Print() override; + void SubmitForm(const std::string& url, + const void* data, + int length) override; std::unique_ptr CreateUrlLoader() override; void DocumentLoadComplete() override; void DocumentLoadFailed() override; @@ -157,6 +171,16 @@ class PdfViewPluginBase : public PDFEngine::Client, return notified_browser_about_unsupported_feature_; } + void InitializeEngineForTesting(std::unique_ptr engine); + + void set_full_frame_for_testing(bool full_frame) { full_frame_ = full_frame; } + + DocumentLoadState document_load_state_for_testing() const { + return document_load_state_; + } + + bool edit_mode_for_testing() const { return edit_mode_; } + protected: struct BackgroundPart { gfx::Rect location; @@ -166,9 +190,22 @@ class PdfViewPluginBase : public PDFEngine::Client, PdfViewPluginBase(); ~PdfViewPluginBase() override; - // Initializes the main `PDFiumEngine` with `engine`. Any existing engine will - // be replaced. `engine` must not be nullptr. - void InitializeEngine(std::unique_ptr engine); + // Performs initialization common to all implementations of this plugin. + // `engine` should be an appropriately-configured PDF engine, and + // `embedder_origin` should be the origin of the plugin's embedder. The other + // parameters come from the corresponding plugin attributes. + void InitializeBase(std::unique_ptr engine, + base::StringPiece embedder_origin, + base::StringPiece src_url, + base::StringPiece original_url, + bool full_frame, + SkColor background_color, + bool has_edits); + + // Creates a new `PDFiumEngine`. + virtual std::unique_ptr CreateEngine( + PDFEngine::Client* client, + PDFiumFormFiller::ScriptOption script_option); // Destroys the main `PDFiumEngine`. Subclasses should call this method in // their destructor to ensure the engine is destroyed first. @@ -181,11 +218,9 @@ class PdfViewPluginBase : public PDFEngine::Client, const PDFiumEngine* engine() const { return engine_.get(); } PDFiumEngine* engine() { return engine_.get(); } - void ValidateDocumentUrl(base::StringPiece document_url); - // Starts loading `url`. If `is_print_preview` is `true`, load for print // preview instead of normal PDF viewing. - void LoadUrl(const std::string& url, bool is_print_preview); + void LoadUrl(base::StringPiece url, bool is_print_preview); // Gets a weak pointer with a lifetime matching the derived class. virtual base::WeakPtr GetWeakPtr() = 0; @@ -194,8 +229,11 @@ class PdfViewPluginBase : public PDFEngine::Client, // frame's origin. virtual std::unique_ptr CreateUrlLoaderInternal() = 0; - // Handles `LoadUrl()` result. - virtual void DidOpen(std::unique_ptr loader, int32_t result) = 0; + // Rewrites the request URL just before sending to the URL loader. + // + // TODO(crbug.com/1238829): This is a workaround for Pepper not supporting + // chrome-untrusted://print/ URLs. + virtual std::string RewriteRequestUrl(base::StringPiece url) const; bool HandleInputEvent(const blink::WebInputEvent& event); @@ -235,11 +273,11 @@ class PdfViewPluginBase : public PDFEngine::Client, // Returns the plugin-specific image data buffer. virtual Image GetPluginImageData() const; - // Updates the geometry of the plugin and its image data if the view's - // size or scale has changed. `new_view_rect` must be in CSS pixels (without - // device scale applied). - void UpdateGeometryOnViewChanged(const gfx::Rect& new_view_rect, - float new_device_scale); + // Updates the geometry of the plugin and its image data if the plugin rect + // or the device scale has changed. `new_plugin_rect` must be in device + // pixels (with the device scale applied). + void UpdateGeometryOnPluginRectChanged(const gfx::Rect& new_plugin_rect, + float new_device_scale); // A helper of OnGeometryChanged() which updates the available area and // the background parts, and notifies the PDF engine of geometry changes. @@ -249,11 +287,9 @@ class PdfViewPluginBase : public PDFEngine::Client, // aren't painted by the PDF engine). void CalculateBackgroundParts(); - // Repaints the plugin contents based on the current scroll position. - void UpdateScroll(); - - // Bound the given scroll position to the document. - gfx::PointF BoundScrollPositionToDocument(const gfx::PointF& scroll_position); + // Updates the scroll position. `scroll_offset` is in CSS pixels relative to + // the scroll origin (which depends on the UI direction). + void UpdateScroll(const gfx::Vector2dF& scroll_offset); // Computes document width/height in device pixels, based on current zoom and // device scale @@ -289,6 +325,17 @@ class PdfViewPluginBase : public PDFEngine::Client, virtual void SetAccessibilityViewportInfo( const AccessibilityViewportInfo& viewport_info) = 0; + // Find handlers. + bool StartFind(const std::string& text, bool case_sensitive); + void SelectFindResult(bool forward); + void StopFind(); + + // Notify the plugin container about the total matches for a find request. + virtual void NotifyFindResultsChanged(int total, bool final_result) = 0; + + // Notify the frame about the tickmarks for the find request. + virtual void NotifyFindTickmarks(const std::vector& tickmarks) = 0; + // Returns the print preset options for the document. blink::WebPrintPresetOptions GetPrintPresetOptions(); @@ -342,8 +389,6 @@ class PdfViewPluginBase : public PDFEngine::Client, // Records user actions. virtual void UserMetricsRecordAction(const std::string& action) = 0; - void set_url(const std::string& url) { url_ = url; } - ui::mojom::CursorType cursor_type() const { return cursor_type_; } void set_cursor_type(ui::mojom::CursorType cursor_type) { cursor_type_ = cursor_type; @@ -352,7 +397,6 @@ class PdfViewPluginBase : public PDFEngine::Client, const std::string& link_under_cursor() const { return link_under_cursor_; } bool full_frame() const { return full_frame_; } - void set_full_frame(bool full_frame) { full_frame_ = full_frame; } SkBitmap& mutable_image_data() { return image_data_; } @@ -362,10 +406,6 @@ class PdfViewPluginBase : public PDFEngine::Client, const gfx::Rect& plugin_rect() const { return plugin_rect_; } - void SetBackgroundColor(SkColor background_color) { - background_color_ = background_color; - } - // Sets the new zoom scale. void SetZoom(double scale); @@ -373,34 +413,16 @@ class PdfViewPluginBase : public PDFEngine::Client, float device_scale() const { return device_scale_; } - void set_scroll_position(const gfx::Point& scroll_position) { - scroll_position_ = scroll_position; - } - - bool stop_scrolling() const { return stop_scrolling_; } - - DocumentLoadState document_load_state() const { return document_load_state_; } - void set_document_load_state(DocumentLoadState state) { - document_load_state_ = state; - } - AccessibilityState accessibility_state() const { return accessibility_state_; } - bool edit_mode() const { return edit_mode_; } - void set_edit_mode(bool edit_mode) { edit_mode_ = edit_mode; } - - void set_is_print_preview(bool is_print_preview) { - is_print_preview_ = is_print_preview; - } - - static bool IsPrintPreviewUrl(base::StringPiece url); - static constexpr bool IsSaveDataSizeValid(size_t size) { return size > 0 && size <= kMaximumSavedFileSize; } + static base::Value::DictStorage DictFromRect(const gfx::Rect& rect); + private: // Message handlers. void HandleDisplayAnnotationsMessage(const base::Value& message); @@ -459,6 +481,8 @@ class PdfViewPluginBase : public PDFEngine::Client, // Starts loading accessibility information. void LoadAccessibility(); + void ResetRecentlySentFindUpdate(int32_t /*unused_but_required*/); + // Records metrics about the document metadata. void RecordDocumentMetrics(); @@ -475,9 +499,15 @@ class PdfViewPluginBase : public PDFEngine::Client, int32_t max, uint32_t bucket_count); + // Handles `LoadUrl()` result. + void DidOpen(std::unique_ptr loader, int32_t result); + // Handles `LoadUrl()` result for print preview. void DidOpenPreview(std::unique_ptr loader, int32_t result); + // Handles `Open()` result for `form_loader_`. + void DidFormOpen(int32_t result); + // Performs tasks necessary when the document is loaded in print preview mode. void OnPrintPreviewLoaded(); @@ -487,7 +517,7 @@ class PdfViewPluginBase : public PDFEngine::Client, // Process the preview page data information. `src_url` specifies the preview // page data location. The `src_url` is in the format: - // chrome://print/id/page_number/print.pdf + // chrome-untrusted://print/id/page_number/print.pdf // `dest_page_index` specifies the blank page index that needs to be replaced // with the new page data. void ProcessPreviewPageInfo(const std::string& src_url, int dest_page_index); @@ -557,12 +587,12 @@ class PdfViewPluginBase : public PDFEngine::Client, // True if we request a new bitmap rendering. bool needs_reraster_ = true; - // The scroll position in CSS pixels. - gfx::Point scroll_position_; + // The UI direction. + base::i18n::TextDirection ui_direction_ = base::i18n::UNKNOWN_DIRECTION; - // The scroll position for the last raster, before any transformations are - // applied. - gfx::PointF scroll_position_at_last_raster_; + // The scroll offset for the last raster in CSS pixels, before any + // transformations are applied. + gfx::Vector2dF scroll_offset_at_last_raster_; // If this is true, then don't scroll the plugin in response to the messages // from DidChangeView() or HandleUpdateScrollMessage(). This will be true when @@ -595,6 +625,13 @@ class PdfViewPluginBase : public PDFEngine::Client, // reconstructing the tree for new document layouts. int32_t next_accessibility_page_index_ = 0; + // Whether an update to the number of find results found was sent less than + // `kFindResultCooldownMs` milliseconds ago. + bool recently_sent_find_update_ = false; + + // Stores the tickmarks to be shown for the current find results. + std::vector tickmarks_; + // Keeps track of which unsupported features have been reported to avoid // spamming the metrics if a feature shows up many times per document. base::flat_set unsupported_features_reported_; @@ -606,6 +643,9 @@ class PdfViewPluginBase : public PDFEngine::Client, // Whether the document is in edit mode. bool edit_mode_ = false; + // Used for submitting forms. + std::unique_ptr form_loader_; + // Assigned a value only between `PrintBegin()` and `PrintEnd()` calls. absl::optional print_params_; diff --git a/chromium/pdf/pdf_view_plugin_base_unittest.cc b/chromium/pdf/pdf_view_plugin_base_unittest.cc index e1f7219d303..d1ab7153485 100644 --- a/chromium/pdf/pdf_view_plugin_base_unittest.cc +++ b/chromium/pdf/pdf_view_plugin_base_unittest.cc @@ -10,21 +10,25 @@ #include #include "base/containers/contains.h" -#include "base/containers/flat_set.h" +#include "base/cxx17_backports.h" #include "base/memory/weak_ptr.h" +#include "base/strings/string_piece.h" #include "base/test/icu_test_util.h" +#include "base/test/values_test_util.h" #include "base/time/time.h" #include "base/values.h" #include "pdf/accessibility_structs.h" #include "pdf/buildflags.h" #include "pdf/content_restriction.h" #include "pdf/document_attachment_info.h" +#include "pdf/document_layout.h" #include "pdf/document_metadata.h" #include "pdf/pdf_engine.h" -#include "pdf/pdfium/pdfium_engine.h" +#include "pdf/pdfium/pdfium_form_filler.h" #include "pdf/ppapi_migration/callback.h" #include "pdf/ppapi_migration/graphics.h" #include "pdf/ppapi_migration/url_loader.h" +#include "pdf/test/test_pdfium_engine.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -35,83 +39,17 @@ namespace chrome_pdf { namespace { +using ::testing::ByMove; +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::StrEq; + // Keep it in-sync with the `kFinalFallbackName` returned by // net::GetSuggestedFilename(). constexpr char kDefaultDownloadFileName[] = "download"; -// Dummy data to save. -constexpr uint8_t kSaveData[] = {'1', '2', '3'}; - -// Page number. -constexpr uint32_t kPageNumber = 13u; - -class TestPDFiumEngine : public PDFiumEngine { - public: - explicit TestPDFiumEngine(PDFEngine::Client* client) - : PDFiumEngine(client, PDFiumFormFiller::ScriptOption::kNoJavaScript) {} - - TestPDFiumEngine(const TestPDFiumEngine&) = delete; - - TestPDFiumEngine& operator=(const TestPDFiumEngine&) = delete; - - ~TestPDFiumEngine() override = default; - - bool HasPermission(DocumentPermission permission) const override { - return base::Contains(permissions_, permission); - } - - const std::vector& GetDocumentAttachmentInfoList() - const override { - return doc_attachment_info_list_; - } - - const DocumentMetadata& GetDocumentMetadata() const override { - return metadata_; - } - - int GetNumberOfPages() const override { - return static_cast(kPageNumber); - } - - base::Value GetBookmarks() override { - // Return an empty bookmark list. - return base::Value(base::Value::Type::LIST); - } - - uint32_t GetLoadedByteSize() override { return sizeof(kSaveData); } - - bool ReadLoadedBytes(uint32_t length, void* buffer) override { - DCHECK_LE(length, GetLoadedByteSize()); - memcpy(buffer, kSaveData, length); - return true; - } - - std::vector GetSaveData() override { - return std::vector(std::begin(kSaveData), std::end(kSaveData)); - } - - void SetPermissions(const std::vector& permissions) { - permissions_.clear(); - - for (auto& permission : permissions) - permissions_.insert(permission); - } - - protected: - std::vector& doc_attachment_info_list() { - return doc_attachment_info_list_; - } - - DocumentMetadata& metadata() { return metadata_; } - - private: - std::vector doc_attachment_info_list_; - - DocumentMetadata metadata_; - - base::flat_set permissions_; -}; - class TestPDFiumEngineWithDocInfo : public TestPDFiumEngine { public: explicit TestPDFiumEngineWithDocInfo(PDFEngine::Client* client) @@ -203,14 +141,15 @@ class FakePdfViewPluginBase : public PdfViewPluginBase { public: // Public for testing. using PdfViewPluginBase::accessibility_state; - using PdfViewPluginBase::document_load_state; - using PdfViewPluginBase::edit_mode; using PdfViewPluginBase::engine; using PdfViewPluginBase::full_frame; using PdfViewPluginBase::HandleMessage; - using PdfViewPluginBase::InitializeEngine; - using PdfViewPluginBase::set_document_load_state; - using PdfViewPluginBase::set_full_frame; + using PdfViewPluginBase::LoadUrl; + using PdfViewPluginBase::SetZoom; + using PdfViewPluginBase::UpdateGeometryOnPluginRectChanged; + using PdfViewPluginBase::UpdateScroll; + + MOCK_METHOD(std::string, GetURL, (), (override)); MOCK_METHOD(bool, Confirm, (const std::string&), (override)); @@ -244,6 +183,11 @@ class FakePdfViewPluginBase : public PdfViewPluginBase { (const base::Location&, ResultCallback, int32_t, base::TimeDelta), (override)); + MOCK_METHOD(std::unique_ptr, + CreateEngine, + (PDFEngine::Client*, PDFiumFormFiller::ScriptOption), + (override)); + base::WeakPtr GetWeakPtr() override { return weak_factory_.GetWeakPtr(); } @@ -253,8 +197,6 @@ class FakePdfViewPluginBase : public PdfViewPluginBase { (), (override)); - MOCK_METHOD(void, DidOpen, (std::unique_ptr, int32_t), (override)); - void SendMessage(base::Value message) override { sent_messages_.push_back(std::move(message)); } @@ -283,6 +225,13 @@ class FakePdfViewPluginBase : public PdfViewPluginBase { (const AccessibilityViewportInfo&), (override)); + MOCK_METHOD(void, NotifyFindResultsChanged, (int, bool), (override)); + + MOCK_METHOD(void, + NotifyFindTickmarks, + (const std::vector&), + (override)); + MOCK_METHOD(void, SetContentRestrictions, (int), (override)); MOCK_METHOD(void, SetPluginCanSave, (bool), (override)); @@ -302,6 +251,8 @@ class FakePdfViewPluginBase : public PdfViewPluginBase { MOCK_METHOD(void, UserMetricsRecordAction, (const std::string&), (override)); + void clear_sent_messages() { sent_messages_.clear(); } + const std::vector& sent_messages() const { return sent_messages_; } @@ -312,6 +263,17 @@ class FakePdfViewPluginBase : public PdfViewPluginBase { base::WeakPtrFactory weak_factory_{this}; }; +class MockUrlLoader : public UrlLoader { + public: + MOCK_METHOD(void, GrantUniversalAccess, (), (override)); + MOCK_METHOD(void, Open, (const UrlRequest&, ResultCallback), (override)); + MOCK_METHOD(void, + ReadResponseBody, + (base::span, ResultCallback), + (override)); + MOCK_METHOD(void, Close, (), (override)); +}; + base::Value CreateExpectedFormTextFieldFocusChangeResponse() { base::Value message(base::Value::Type::DICTIONARY); message.SetStringKey("type", "formFocusChange"); @@ -392,13 +354,6 @@ base::Value CreateExpectedNoMetadataResponse() { return message; } -base::Value CreateExpectedLoadingProgressResponse() { - base::Value message(base::Value::Type::DICTIONARY); - message.SetStringKey("type", "loadProgress"); - message.SetDoubleKey("progress", 100); - return message; -} - base::Value CreateSaveRequestMessage(PdfViewPluginBase::SaveRequestType type, const std::string& token) { base::Value message(base::Value::Type::DICTIONARY); @@ -413,8 +368,8 @@ base::Value CreateExpectedSaveToBufferResponse(const std::string& token) { expected_response.SetStringKey("type", "saveData"); expected_response.SetStringKey("token", token); expected_response.SetStringKey("fileName", kDefaultDownloadFileName); - expected_response.SetKey("dataToSave", - base::Value(base::make_span(kSaveData))); + expected_response.SetKey( + "dataToSave", base::Value(base::make_span(TestPDFiumEngine::kSaveData))); return expected_response; } @@ -435,9 +390,26 @@ class PdfViewPluginBaseTest : public testing::Test { class PdfViewPluginBaseWithEngineTest : public PdfViewPluginBaseTest { public: void SetUp() override { - std::unique_ptr engine = - std::make_unique(&fake_plugin_); - fake_plugin_.InitializeEngine(std::move(engine)); + auto engine = + std::make_unique>(&fake_plugin_); + fake_plugin_.InitializeEngineForTesting(std::move(engine)); + } + + protected: + void SendDefaultViewportMessage() { + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 2, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": 0, + "yOffset": 0, + "pinchPhase": 0, + })")); } }; @@ -454,7 +426,7 @@ class PdfViewPluginBaseWithDocInfoTest void SetUp() override { std::unique_ptr engine = std::make_unique(&fake_plugin_); - fake_plugin_.InitializeEngine(std::move(engine)); + fake_plugin_.InitializeEngineForTesting(std::move(engine)); // Initialize some arbitrary document information for the engine. static_cast(fake_plugin_.engine()) @@ -465,8 +437,92 @@ class PdfViewPluginBaseWithDocInfoTest using PdfViewPluginBaseWithoutDocInfoTest = PdfViewPluginBaseWithScopedLocaleTest; +TEST_F(PdfViewPluginBaseTest, LoadUrl) { + UrlRequest saved_request; + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal) + .WillOnce([&saved_request]() { + auto mock_loader = std::make_unique>(); + EXPECT_CALL(*mock_loader, Open) + .WillOnce(testing::SaveArg<0>(&saved_request)); + return mock_loader; + }); + + // Note that `is_print_preview` only controls the load callback. */ + fake_plugin_.LoadUrl("fake-url", /*is_print_preview=*/false); + + EXPECT_EQ("fake-url", saved_request.url); + EXPECT_EQ("GET", saved_request.method); + EXPECT_TRUE(saved_request.ignore_redirects); + EXPECT_EQ("", saved_request.custom_referrer_url); + EXPECT_EQ("", saved_request.headers); + EXPECT_EQ("", saved_request.body); + EXPECT_LE(saved_request.buffer_lower_threshold, + saved_request.buffer_upper_threshold); +} + +TEST_F(PdfViewPluginBaseTest, DocumentLoadProgress) { + fake_plugin_.DocumentLoadProgress(10, 200); + + EXPECT_THAT(fake_plugin_.sent_messages(), ElementsAre(base::test::IsJson(R"({ + "type": "loadProgress", + "progress": 5.0, + })"))); +} + +TEST_F(PdfViewPluginBaseTest, DocumentLoadProgressIgnoreSmall) { + fake_plugin_.DocumentLoadProgress(2, 100); + fake_plugin_.clear_sent_messages(); + + fake_plugin_.DocumentLoadProgress(3, 100); + + EXPECT_THAT(fake_plugin_.sent_messages(), IsEmpty()); +} + +TEST_F(PdfViewPluginBaseTest, DocumentLoadProgressMultipleSmall) { + fake_plugin_.DocumentLoadProgress(2, 100); + fake_plugin_.clear_sent_messages(); + + fake_plugin_.DocumentLoadProgress(3, 100); + fake_plugin_.DocumentLoadProgress(4, 100); + + EXPECT_THAT(fake_plugin_.sent_messages(), ElementsAre(base::test::IsJson(R"({ + "type": "loadProgress", + "progress": 4.0, + })"))); +} + +TEST_F(PdfViewPluginBaseTest, DocumentLoadProgressResetByLoadUrl) { + fake_plugin_.DocumentLoadProgress(2, 100); + fake_plugin_.clear_sent_messages(); + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal).WillOnce([]() { + return std::make_unique>(); + }); + + fake_plugin_.LoadUrl("fake-url", /*is_print_preview=*/false); + fake_plugin_.DocumentLoadProgress(3, 100); + + EXPECT_THAT(fake_plugin_.sent_messages(), ElementsAre(base::test::IsJson(R"({ + "type": "loadProgress", + "progress": 3.0, + })"))); +} + +TEST_F(PdfViewPluginBaseTest, + DocumentLoadProgressNotResetByLoadUrlWithPrintPreview) { + fake_plugin_.DocumentLoadProgress(2, 100); + fake_plugin_.clear_sent_messages(); + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal).WillOnce([]() { + return std::make_unique>(); + }); + + fake_plugin_.LoadUrl("fake-url", /*is_print_preview=*/true); + fake_plugin_.DocumentLoadProgress(3, 100); + + EXPECT_THAT(fake_plugin_.sent_messages(), IsEmpty()); +} + TEST_F(PdfViewPluginBaseTest, CreateUrlLoaderInFullFrame) { - fake_plugin_.set_full_frame(true); + fake_plugin_.set_full_frame_for_testing(true); ASSERT_TRUE(fake_plugin_.full_frame()); EXPECT_FALSE(fake_plugin_.GetDidCallStartLoadingForTesting()); @@ -494,13 +550,13 @@ TEST_F(PdfViewPluginBaseTest, CreateUrlLoaderWithoutFullFrame) { TEST_F(PdfViewPluginBaseWithDocInfoTest, DocumentLoadCompleteInFullFramePdfViewerWithAccessibilityEnabled) { // Notify the render frame about document loading. - fake_plugin_.set_full_frame(true); + fake_plugin_.set_full_frame_for_testing(true); ASSERT_TRUE(fake_plugin_.full_frame()); fake_plugin_.CreateUrlLoader(); ASSERT_FALSE(fake_plugin_.IsPrintPreview()); ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); // Change the accessibility state to pending so that accessibility can be // loaded later. @@ -518,12 +574,12 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, fake_plugin_.DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_EQ(PdfViewPluginBase::AccessibilityState::kLoaded, fake_plugin_.accessibility_state()); // Check all the sent messages. - ASSERT_EQ(5u, fake_plugin_.sent_messages().size()); + ASSERT_EQ(4u, fake_plugin_.sent_messages().size()); EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(), fake_plugin_.sent_messages()[0]); EXPECT_EQ(CreateExpectedAttachmentsResponse(), @@ -532,20 +588,18 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, CreateExpectedBookmarksResponse(fake_plugin_.engine()->GetBookmarks()), fake_plugin_.sent_messages()[2]); EXPECT_EQ(CreateExpectedMetadataResponse(), fake_plugin_.sent_messages()[3]); - EXPECT_EQ(CreateExpectedLoadingProgressResponse(), - fake_plugin_.sent_messages()[4]); } TEST_F(PdfViewPluginBaseWithDocInfoTest, DocumentLoadCompleteInFullFramePdfViewerWithAccessibilityDisabled) { // Notify the render frame about document loading. - fake_plugin_.set_full_frame(true); + fake_plugin_.set_full_frame_for_testing(true); ASSERT_TRUE(fake_plugin_.full_frame()); fake_plugin_.CreateUrlLoader(); ASSERT_FALSE(fake_plugin_.IsPrintPreview()); ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); ASSERT_EQ(PdfViewPluginBase::AccessibilityState::kOff, fake_plugin_.accessibility_state()); @@ -560,12 +614,12 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, fake_plugin_.DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_EQ(PdfViewPluginBase::AccessibilityState::kOff, fake_plugin_.accessibility_state()); // Check all the sent messages. - ASSERT_EQ(5u, fake_plugin_.sent_messages().size()); + ASSERT_EQ(4u, fake_plugin_.sent_messages().size()); EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(), fake_plugin_.sent_messages()[0]); EXPECT_EQ(CreateExpectedAttachmentsResponse(), @@ -574,8 +628,6 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, CreateExpectedBookmarksResponse(fake_plugin_.engine()->GetBookmarks()), fake_plugin_.sent_messages()[2]); EXPECT_EQ(CreateExpectedMetadataResponse(), fake_plugin_.sent_messages()[3]); - EXPECT_EQ(CreateExpectedLoadingProgressResponse(), - fake_plugin_.sent_messages()[4]); } TEST_F(PdfViewPluginBaseWithDocInfoTest, @@ -585,7 +637,7 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, ASSERT_FALSE(fake_plugin_.IsPrintPreview()); ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadSuccess")); EXPECT_CALL(fake_plugin_, SetFormFieldInFocus(false)); @@ -596,10 +648,10 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, fake_plugin_.DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); // Check all the sent messages. - ASSERT_EQ(5u, fake_plugin_.sent_messages().size()); + ASSERT_EQ(4u, fake_plugin_.sent_messages().size()); EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(), fake_plugin_.sent_messages()[0]); EXPECT_EQ(CreateExpectedAttachmentsResponse(), @@ -608,8 +660,6 @@ TEST_F(PdfViewPluginBaseWithDocInfoTest, CreateExpectedBookmarksResponse(fake_plugin_.engine()->GetBookmarks()), fake_plugin_.sent_messages()[2]); EXPECT_EQ(CreateExpectedMetadataResponse(), fake_plugin_.sent_messages()[3]); - EXPECT_EQ(CreateExpectedLoadingProgressResponse(), - fake_plugin_.sent_messages()[4]); } TEST_F(PdfViewPluginBaseWithoutDocInfoTest, DocumentLoadCompletePostMessages) { @@ -617,33 +667,31 @@ TEST_F(PdfViewPluginBaseWithoutDocInfoTest, DocumentLoadCompletePostMessages) { ASSERT_FALSE(fake_plugin_.IsPrintPreview()); ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadSuccess")); EXPECT_CALL(fake_plugin_, SetFormFieldInFocus(false)); fake_plugin_.DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); // Check the sent messages when the document doesn't have any metadata, // attachments or bookmarks. - ASSERT_EQ(3u, fake_plugin_.sent_messages().size()); + ASSERT_EQ(2u, fake_plugin_.sent_messages().size()); EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(), fake_plugin_.sent_messages()[0]); EXPECT_EQ(CreateExpectedNoMetadataResponse(), fake_plugin_.sent_messages()[1]); - EXPECT_EQ(CreateExpectedLoadingProgressResponse(), - fake_plugin_.sent_messages()[2]); } TEST_F(PdfViewPluginBaseTest, DocumentLoadFailedWithNotifiedRenderFrame) { // Notify the render frame about document loading. - fake_plugin_.set_full_frame(true); + fake_plugin_.set_full_frame_for_testing(true); ASSERT_TRUE(fake_plugin_.full_frame()); fake_plugin_.CreateUrlLoader(); ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_TRUE(fake_plugin_.GetDidCallStartLoadingForTesting()); EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadFailure")); @@ -651,7 +699,7 @@ TEST_F(PdfViewPluginBaseTest, DocumentLoadFailedWithNotifiedRenderFrame) { fake_plugin_.DocumentLoadFailed(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kFailed, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_FALSE(fake_plugin_.GetDidCallStartLoadingForTesting()); } @@ -661,18 +709,18 @@ TEST_F(PdfViewPluginBaseTest, DocumentLoadFailedWithoutNotifiedRenderFrame) { EXPECT_FALSE(fake_plugin_.GetDidCallStartLoadingForTesting()); ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadFailure")); EXPECT_CALL(fake_plugin_, PluginDidStopLoading()).Times(0); fake_plugin_.DocumentLoadFailed(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kFailed, - fake_plugin_.document_load_state()); + fake_plugin_.document_load_state_for_testing()); EXPECT_FALSE(fake_plugin_.GetDidCallStartLoadingForTesting()); } TEST_F(PdfViewPluginBaseTest, DocumentHasUnsupportedFeatureInFullFrame) { - fake_plugin_.set_full_frame(true); + fake_plugin_.set_full_frame_for_testing(true); ASSERT_TRUE(fake_plugin_.full_frame()); // Arbitrary feature names and their matching metric names. @@ -738,7 +786,7 @@ TEST_F(PdfViewPluginBaseTest, EnteredEditMode) { base::Value expected_response(base::Value::Type::DICTIONARY); expected_response.SetStringKey("type", "setIsEditing"); - EXPECT_TRUE(fake_plugin_.edit_mode()); + EXPECT_TRUE(fake_plugin_.edit_mode_for_testing()); ASSERT_EQ(1u, fake_plugin_.sent_messages().size()); EXPECT_EQ(expected_response, fake_plugin_.sent_messages()[0]); } @@ -747,7 +795,7 @@ using PdfViewPluginBaseSaveTest = PdfViewPluginBaseWithEngineTest; #if BUILDFLAG(ENABLE_INK) TEST_F(PdfViewPluginBaseSaveTest, SaveAnnotationInNonEditMode) { - ASSERT_FALSE(fake_plugin_.edit_mode()); + ASSERT_FALSE(fake_plugin_.edit_mode_for_testing()); static constexpr char kSaveAnnotInNonEditModeToken[] = "save-annot-in-non-edit-mode-token"; @@ -766,7 +814,7 @@ TEST_F(PdfViewPluginBaseSaveTest, SaveAnnotationInNonEditMode) { TEST_F(PdfViewPluginBaseSaveTest, SaveAnnotationInEditMode) { fake_plugin_.EnteredEditMode(); - ASSERT_TRUE(fake_plugin_.edit_mode()); + ASSERT_TRUE(fake_plugin_.edit_mode_for_testing()); static constexpr char kSaveAnnotInEditModeToken[] = "save-annot-in-edit-mode-token"; @@ -785,7 +833,7 @@ TEST_F(PdfViewPluginBaseSaveTest, SaveAnnotationInEditMode) { #endif // BUILDFLAG(ENABLE_INK) TEST_F(PdfViewPluginBaseSaveTest, SaveOriginalInNonEditMode) { - ASSERT_FALSE(fake_plugin_.edit_mode()); + ASSERT_FALSE(fake_plugin_.edit_mode_for_testing()); static constexpr char kSaveOriginalInNonEditModeToken[] = "save-original-in-non-edit-mode-token"; @@ -806,7 +854,7 @@ TEST_F(PdfViewPluginBaseSaveTest, SaveOriginalInNonEditMode) { TEST_F(PdfViewPluginBaseSaveTest, SaveOriginalInEditMode) { fake_plugin_.EnteredEditMode(); - ASSERT_TRUE(fake_plugin_.edit_mode()); + ASSERT_TRUE(fake_plugin_.edit_mode_for_testing()); static constexpr char kSaveOriginalInEditModeToken[] = "save-original-in-edit-mode-token"; @@ -828,7 +876,7 @@ TEST_F(PdfViewPluginBaseSaveTest, SaveOriginalInEditMode) { #if BUILDFLAG(ENABLE_INK) TEST_F(PdfViewPluginBaseSaveTest, SaveEditedInNonEditMode) { - ASSERT_FALSE(fake_plugin_.edit_mode()); + ASSERT_FALSE(fake_plugin_.edit_mode_for_testing()); static constexpr char kSaveEditedInNonEditModeToken[] = "save-edited-in-non-edit-mode"; @@ -847,7 +895,7 @@ TEST_F(PdfViewPluginBaseSaveTest, SaveEditedInNonEditMode) { TEST_F(PdfViewPluginBaseSaveTest, SaveEditedInEditMode) { fake_plugin_.EnteredEditMode(); - ASSERT_TRUE(fake_plugin_.edit_mode()); + ASSERT_TRUE(fake_plugin_.edit_mode_for_testing()); static constexpr char kSaveEditedInEditModeToken[] = "save-edited-in-edit-mode-token"; @@ -874,6 +922,284 @@ TEST_F(PdfViewPluginBaseTest, HandleSetBackgroundColorMessage) { EXPECT_EQ(kNewBackgroundColor, fake_plugin_.GetBackgroundColor()); } +TEST_F(PdfViewPluginBaseWithEngineTest, + HandleViewportMessageBeforeDocumentLoadComplete) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ApplyDocumentLayout(DocumentLayout::Options())); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 0, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": 0, + "yOffset": 0, + "pinchPhase": 0, + })")); + + EXPECT_THAT(fake_plugin_.sent_messages(), IsEmpty()); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, + HandleViewportMessageAfterDocumentLoadComplete) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ApplyDocumentLayout(DocumentLayout::Options())); + + fake_plugin_.DocumentLoadComplete(); + fake_plugin_.clear_sent_messages(); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 0, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": 0, + "yOffset": 0, + "pinchPhase": 0, + })")); + + EXPECT_THAT(fake_plugin_.sent_messages(), ElementsAre(base::test::IsJson(R"({ + "type": "loadProgress", + "progress": 100.0, + })"))); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, HandleViewportMessageSubsequently) { + auto* engine = static_cast(fake_plugin_.engine()); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 0, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": 0, + "yOffset": 0, + "pinchPhase": 0, + })")); + fake_plugin_.clear_sent_messages(); + + DocumentLayout::Options two_up_options; + two_up_options.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd); + EXPECT_CALL(*engine, ApplyDocumentLayout(two_up_options)); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 0, + "defaultPageOrientation": 0, + "twoUpViewEnabled": true, + }, + "xOffset": 0, + "yOffset": 0, + "pinchPhase": 0, + })")); + + EXPECT_THAT(fake_plugin_.sent_messages(), IsEmpty()); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, HandleViewportMessageScroll) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + EXPECT_CALL(*engine, ScrolledToXPosition(2)); + EXPECT_CALL(*engine, ScrolledToYPosition(3)); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 2, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": 2, + "yOffset": 3, + "pinchPhase": 0, + })")); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, + HandleViewportMessageScrollRightToLeft) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + EXPECT_CALL(*engine, ScrolledToXPosition(2)); + EXPECT_CALL(*engine, ScrolledToYPosition(3)); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 1, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": 2, + "yOffset": 3, + "pinchPhase": 0, + })")); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, + HandleViewportMessageScrollRightToLeftInPrintPreview) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + EXPECT_CALL(*engine, ScrolledToXPosition(14)); + EXPECT_CALL(*engine, ScrolledToYPosition(3)); + EXPECT_CALL(fake_plugin_, IsPrintPreview).WillRepeatedly(Return(true)); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "viewport", + "userInitiated": false, + "zoom": 1, + "layoutOptions": { + "direction": 1, + "defaultPageOrientation": 0, + "twoUpViewEnabled": false, + }, + "xOffset": -2, + "yOffset": 3, + "pinchPhase": 0, + })")); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, UpdateScroll) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ScrolledToXPosition(0)); + EXPECT_CALL(*engine, ScrolledToYPosition(0)); + + fake_plugin_.UpdateScroll({0, 0}); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, UpdateScrollStopped) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ScrolledToXPosition).Times(0); + EXPECT_CALL(*engine, ScrolledToYPosition).Times(0); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "stopScrolling", + })")); + fake_plugin_.UpdateScroll({0, 0}); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, UpdateScrollUnderflow) { + auto* engine = static_cast(fake_plugin_.engine()); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + SendDefaultViewportMessage(); + EXPECT_CALL(*engine, ScrolledToXPosition(0)); + EXPECT_CALL(*engine, ScrolledToYPosition(0)); + + fake_plugin_.UpdateScroll({-1, -1}); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, UpdateScrollOverflow) { + auto* engine = static_cast(fake_plugin_.engine()); + fake_plugin_.UpdateGeometryOnPluginRectChanged({3, 2}, 1.0f); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + SendDefaultViewportMessage(); + + EXPECT_CALL(*engine, ScrolledToXPosition(13)); + EXPECT_CALL(*engine, ScrolledToYPosition(7)); + + fake_plugin_.UpdateScroll({14, 8}); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, UpdateScrollOverflowZoomed) { + auto* engine = static_cast(fake_plugin_.engine()); + fake_plugin_.UpdateGeometryOnPluginRectChanged({3, 2}, 1.0f); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + SendDefaultViewportMessage(); + fake_plugin_.SetZoom(2.0); + + EXPECT_CALL(*engine, ScrolledToXPosition(29)); + EXPECT_CALL(*engine, ScrolledToYPosition(16)); + + fake_plugin_.UpdateScroll({30, 17}); +} + +TEST_F(PdfViewPluginBaseWithEngineTest, UpdateScrollScaled) { + auto* engine = static_cast(fake_plugin_.engine()); + fake_plugin_.UpdateGeometryOnPluginRectChanged({3, 2}, 2.0f); + EXPECT_CALL(*engine, ApplyDocumentLayout) + .WillRepeatedly(Return(gfx::Size(16, 9))); + SendDefaultViewportMessage(); + + EXPECT_CALL(*engine, ScrolledToXPosition(4)); + EXPECT_CALL(*engine, ScrolledToYPosition(2)); + + fake_plugin_.UpdateScroll({2, 1}); +} + +TEST_F(PdfViewPluginBaseTest, HandleResetPrintPreviewModeMessage) { + EXPECT_CALL(fake_plugin_, IsPrintPreview).WillRepeatedly(Return(true)); + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal).WillRepeatedly([]() { + return std::make_unique>(); + }); + + auto engine = + std::make_unique>(&fake_plugin_); + EXPECT_CALL(*engine, ZoomUpdated); + EXPECT_CALL(*engine, PageOffsetUpdated); + EXPECT_CALL(*engine, PluginSizeUpdated); + EXPECT_CALL(*engine, SetGrayscale(false)); + EXPECT_CALL(fake_plugin_, + CreateEngine(&fake_plugin_, + PDFiumFormFiller::ScriptOption::kNoJavaScript)) + .WillOnce(Return(ByMove(std::move(engine)))); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "resetPrintPreviewMode", + "url": "chrome-untrusted://print/0/0/print.pdf", + "grayscale": false, + "pageCount": 1, + })")); +} + +TEST_F(PdfViewPluginBaseTest, HandleResetPrintPreviewModeMessageSetGrayscale) { + EXPECT_CALL(fake_plugin_, IsPrintPreview).WillRepeatedly(Return(true)); + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal).WillRepeatedly([]() { + return std::make_unique>(); + }); + + auto engine = + std::make_unique>(&fake_plugin_); + EXPECT_CALL(*engine, ZoomUpdated); + EXPECT_CALL(*engine, PageOffsetUpdated); + EXPECT_CALL(*engine, PluginSizeUpdated); + EXPECT_CALL(*engine, SetGrayscale(true)); + EXPECT_CALL(fake_plugin_, + CreateEngine(&fake_plugin_, + PDFiumFormFiller::ScriptOption::kNoJavaScript)) + .WillOnce(Return(ByMove(std::move(engine)))); + + fake_plugin_.HandleMessage(base::test::ParseJson(R"({ + "type": "resetPrintPreviewMode", + "url": "chrome-untrusted://print/0/0/print.pdf", + "grayscale": true, + "pageCount": 1, + })")); +} + TEST_F(PdfViewPluginBaseWithEngineTest, GetContentRestrictions) { auto* engine = static_cast(fake_plugin_.engine()); static constexpr int kContentRestrictionCutPaste = @@ -932,7 +1258,7 @@ TEST_F(PdfViewPluginBaseWithEngineTest, GetAccessibilityDocInfo) { engine->SetPermissions({}); AccessibilityDocInfo doc_info = fake_plugin_.GetAccessibilityDocInfo(); - EXPECT_EQ(kPageNumber, doc_info.page_count); + EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_FALSE(doc_info.text_accessible); EXPECT_FALSE(doc_info.text_copyable); @@ -940,7 +1266,7 @@ TEST_F(PdfViewPluginBaseWithEngineTest, GetAccessibilityDocInfo) { engine->SetPermissions({DocumentPermission::kCopy}); doc_info = fake_plugin_.GetAccessibilityDocInfo(); - EXPECT_EQ(kPageNumber, doc_info.page_count); + EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_FALSE(doc_info.text_accessible); EXPECT_TRUE(doc_info.text_copyable); @@ -948,7 +1274,7 @@ TEST_F(PdfViewPluginBaseWithEngineTest, GetAccessibilityDocInfo) { engine->SetPermissions({DocumentPermission::kCopyAccessible}); doc_info = fake_plugin_.GetAccessibilityDocInfo(); - EXPECT_EQ(kPageNumber, doc_info.page_count); + EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_TRUE(doc_info.text_accessible); EXPECT_FALSE(doc_info.text_copyable); @@ -957,9 +1283,79 @@ TEST_F(PdfViewPluginBaseWithEngineTest, GetAccessibilityDocInfo) { {DocumentPermission::kCopy, DocumentPermission::kCopyAccessible}); doc_info = fake_plugin_.GetAccessibilityDocInfo(); - EXPECT_EQ(kPageNumber, doc_info.page_count); + EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_TRUE(doc_info.text_accessible); EXPECT_TRUE(doc_info.text_copyable); } +class PdfViewPluginBaseSubmitFormTest : public PdfViewPluginBaseTest { + public: + void SubmitForm(const std::string& url, + base::StringPiece form_data = "data") { + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal).WillOnce([this]() { + auto mock_loader = std::make_unique>(); + EXPECT_CALL(*mock_loader, Open).WillOnce(testing::SaveArg<0>(&request_)); + return mock_loader; + }); + + fake_plugin_.SubmitForm(url, form_data.data(), form_data.size()); + } + + void SubmitFailingForm(const std::string& url) { + EXPECT_CALL(fake_plugin_, CreateUrlLoaderInternal).Times(0); + constexpr char kFormData[] = "form data"; + fake_plugin_.SubmitForm(url, kFormData, base::size(kFormData)); + } + + protected: + UrlRequest request_; +}; + +TEST_F(PdfViewPluginBaseSubmitFormTest, RequestMethodAndBody) { + EXPECT_CALL(fake_plugin_, GetURL) + .WillOnce(Return("https://www.example.com/path/to/the.pdf")); + constexpr char kFormData[] = "form data"; + SubmitForm(/*url=*/"", kFormData); + EXPECT_EQ(request_.method, "POST"); + EXPECT_THAT(request_.body, StrEq(kFormData)); +} + +TEST_F(PdfViewPluginBaseSubmitFormTest, RelativeUrl) { + EXPECT_CALL(fake_plugin_, GetURL) + .WillOnce(Return("https://www.example.com/path/to/the.pdf")); + SubmitForm("relative_endpoint"); + EXPECT_EQ(request_.url, "https://www.example.com/path/to/relative_endpoint"); +} + +TEST_F(PdfViewPluginBaseSubmitFormTest, NoRelativeUrl) { + EXPECT_CALL(fake_plugin_, GetURL) + .WillOnce(Return("https://www.example.com/path/to/the.pdf")); + SubmitForm(""); + EXPECT_EQ(request_.url, "https://www.example.com/path/to/the.pdf"); +} + +TEST_F(PdfViewPluginBaseSubmitFormTest, AbsoluteUrl) { + EXPECT_CALL(fake_plugin_, GetURL) + .WillOnce(Return("https://a.example.com/path/to/the.pdf")); + SubmitForm("https://b.example.com/relative_endpoint"); + EXPECT_EQ(request_.url, "https://b.example.com/relative_endpoint"); +} + +TEST_F(PdfViewPluginBaseSubmitFormTest, EmptyDocumentUrl) { + EXPECT_CALL(fake_plugin_, GetURL).WillOnce(Return(std::string())); + SubmitFailingForm("relative_endpoint"); +} + +TEST_F(PdfViewPluginBaseSubmitFormTest, RelativeUrlInvalidDocumentUrl) { + EXPECT_CALL(fake_plugin_, GetURL) + .WillOnce(Return(R"(https://www.%B%Ad.com/path/to/the.pdf)")); + SubmitFailingForm("relative_endpoint"); +} + +TEST_F(PdfViewPluginBaseSubmitFormTest, AbsoluteUrlInvalidDocumentUrl) { + EXPECT_CALL(fake_plugin_, GetURL) + .WillOnce(Return(R"(https://www.%B%Ad.com/path/to/the.pdf)")); + SubmitFailingForm("https://wwww.example.com"); +} + } // namespace chrome_pdf diff --git a/chromium/pdf/pdf_view_web_plugin.cc b/chromium/pdf/pdf_view_web_plugin.cc index 470e5ddfa7d..29f922ed743 100644 --- a/chromium/pdf/pdf_view_web_plugin.cc +++ b/chromium/pdf/pdf_view_web_plugin.cc @@ -13,6 +13,7 @@ #include #include "base/check_op.h" +#include "base/i18n/char_iterator.h" #include "base/i18n/string_search.h" #include "base/no_destructor.h" #include "base/notreached.h" @@ -47,6 +48,7 @@ #include "third_party/blink/public/common/metrics/document_update_reason.h" #include "third_party/blink/public/mojom/input/focus_type.mojom-shared.h" #include "third_party/blink/public/platform/web_input_event_result.h" +#include "third_party/blink/public/platform/web_security_origin.h" #include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_text_input_type.h" #include "third_party/blink/public/platform/web_url.h" @@ -56,6 +58,7 @@ #include "third_party/blink/public/web/web_associated_url_loader.h" #include "third_party/blink/public/web/web_associated_url_loader_options.h" #include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_frame.h" #include "third_party/blink/public/web/web_frame_widget.h" #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_local_frame_client.h" @@ -64,18 +67,19 @@ #include "third_party/blink/public/web/web_print_preset_options.h" #include "third_party/blink/public/web/web_view.h" #include "third_party/blink/public/web/web_widget.h" +#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkRefCnt.h" #include "ui/base/cursor/cursor.h" #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" #include "ui/display/screen_info.h" +#include "ui/events/base_event_utils.h" #include "ui/events/blink/blink_event_util.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" -#include "ui/gfx/geometry/scroll_offset.h" +#include "ui/gfx/geometry/skia_conversions.h" #include "ui/gfx/range/range.h" -#include "ui/gfx/skia_util.h" #include "url/gurl.h" #include "v8/include/v8.h" @@ -141,6 +145,11 @@ class BlinkContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper { void Invalidate() override { container_->Invalidate(); } + void RequestTouchEventType( + blink::WebPluginContainer::TouchEventRequestType request_type) override { + container_->RequestTouchEventType(request_type); + } + void ReportFindInPageMatchCount(int identifier, int total, bool final_update) override { @@ -166,16 +175,20 @@ class BlinkContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper { } void Alert(const blink::WebString& message) override { - GetFrame()->Alert(message); + blink::WebLocalFrame* frame = GetFrame(); + if (frame) + frame->Alert(message); } bool Confirm(const blink::WebString& message) override { - return GetFrame()->Confirm(message); + blink::WebLocalFrame* frame = GetFrame(); + return frame && frame->Confirm(message); } blink::WebString Prompt(const blink::WebString& message, const blink::WebString& default_value) override { - return GetFrame()->Prompt(message, default_value); + blink::WebLocalFrame* frame = GetFrame(); + return frame ? frame->Prompt(message, default_value) : blink::WebString(); } void TextSelectionChanged(const blink::WebString& selection_text, @@ -210,6 +223,18 @@ class BlinkContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper { widget->UpdateSelectionBounds(); } + std::string GetEmbedderOriginString() override { + auto* frame = GetFrame(); + if (!frame) + return {}; + + auto* parent_frame = frame->Parent(); + if (!parent_frame) + return {}; + + return GURL(parent_frame->GetSecurityOrigin().ToString().Utf8()).spec(); + } + blink::WebLocalFrame* GetFrame() override { return container_->GetDocument().GetFrame(); } @@ -250,38 +275,30 @@ PdfViewWebPlugin::~PdfViewWebPlugin() = default; bool PdfViewWebPlugin::Initialize(blink::WebPluginContainer* container) { DCHECK_EQ(container->Plugin(), this); - return InitializeCommon(std::make_unique(container)); + return InitializeCommon(std::make_unique(container), + nullptr); } bool PdfViewWebPlugin::InitializeForTesting( - std::unique_ptr container_wrapper) { - return InitializeCommon(std::move(container_wrapper)); + std::unique_ptr container_wrapper, + std::unique_ptr engine) { + return InitializeCommon(std::move(container_wrapper), std::move(engine)); } // Modeled on `OutOfProcessInstance::Init()`. bool PdfViewWebPlugin::InitializeCommon( - std::unique_ptr container_wrapper) { + std::unique_ptr container_wrapper, + std::unique_ptr engine) { container_wrapper_ = std::move(container_wrapper); + post_message_sender_.set_container(Container()); - // Check if the PDF is being loaded in the PDF chrome extension. We only allow - // the plugin to be loaded in the extension and print preview to avoid - // exposing sensitive APIs directly to external websites. - std::string document_url; - auto* container = Container(); - if (container) { - GURL maybe_url(container->GetDocument().Url()); - if (maybe_url.is_valid()) - document_url = maybe_url.possibly_invalid_spec(); - } - - base::StringPiece document_url_piece(document_url); - set_is_print_preview(IsPrintPreviewUrl(document_url_piece)); - // TODO(crbug.com/1123621): Consider calling ValidateDocumentUrl() or - // something like it once the process model has been finalized. + // Allow the plugin to handle touch events. + container_wrapper_->RequestTouchEventType( + blink::WebPluginContainer::kTouchEventRequestTypeRaw); // Allow the plugin to handle find requests. - if (container) - container->UsePluginAsFindHandler(); + if (Container()) + Container()->UsePluginAsFindHandler(); absl::optional params = ParseWebPluginParams(initial_params_); @@ -291,16 +308,19 @@ bool PdfViewWebPlugin::InitializeCommon( if (!params.has_value()) return false; - set_full_frame(params->full_frame); - if (params->background_color.has_value()) - SetBackgroundColor(params->background_color.value()); - PerProcessInitializer::GetInstance().Acquire(); - InitializeEngine(std::make_unique(this, params->script_option)); - LoadUrl(params->src_url, /*is_print_preview=*/false); - set_url(params->original_url); - post_message_sender_.set_container(Container()); + // TODO(crbug.com/1257666): Implement "has-edits" support. + InitializeBase( + engine ? std::move(engine) + : std::make_unique(this, params->script_option), + /*embedder_origin=*/container_wrapper_->GetEmbedderOriginString(), + /*src_url=*/params->src_url, + /*original_url=*/params->original_url, + /*full_frame=*/params->full_frame, + /*background_color=*/ + params->background_color.value_or(SK_ColorTRANSPARENT), + /*has_edits=*/false); return true; } @@ -311,8 +331,8 @@ void PdfViewWebPlugin::Destroy() { DestroyPreviewEngine(); DestroyEngine(); PerProcessInitializer::GetInstance().Release(); - container_wrapper_.reset(); post_message_sender_.set_container(nullptr); + container_wrapper_.reset(); } delete this; @@ -344,19 +364,12 @@ void PdfViewWebPlugin::UpdateAllLifecyclePhases( blink::DocumentUpdateReason reason) {} void PdfViewWebPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) { - // The scale level used to convert DIPs to CSS pixels. - float inverse_scale = 1.0f / (device_scale() * viewport_to_dip_scale_); - - // `rect` is in CSS pixels, and the plugin rect is in DIPs. The plugin rect - // needs to be converted into CSS pixels before calculating the rect area to - // be invalidated. - gfx::Rect plugin_rect_in_css_pixels = - gfx::ScaleToEnclosingRectSafe(plugin_rect(), inverse_scale); - // Clip the intersection of the paint rect and the plugin rect, so that // painting outside the plugin or the paint rect area can be avoided. + // Note: `rect` is in CSS pixels. We need to use `css_plugin_rect_` + // to calculate the intersection. SkRect invalidate_rect = - gfx::RectToSkRect(gfx::IntersectRects(plugin_rect_in_css_pixels, rect)); + gfx::RectToSkRect(gfx::IntersectRects(css_plugin_rect_, rect)); cc::PaintCanvasAutoRestore auto_restore(canvas, /*save=*/true); canvas->clipRect(invalidate_rect); @@ -369,8 +382,8 @@ void PdfViewWebPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) { return; } - if (inverse_scale != 1.0f) - canvas->scale(inverse_scale, inverse_scale); + if (device_to_css_scale_ != 1.0f) + canvas->scale(device_to_css_scale_, device_to_css_scale_); canvas->drawImage(snapshot_, plugin_rect().x(), plugin_rect().y()); } @@ -379,16 +392,19 @@ void PdfViewWebPlugin::UpdateGeometry(const gfx::Rect& window_rect, const gfx::Rect& clip_rect, const gfx::Rect& unobscured_rect, bool is_visible) { - float device_scale = container_wrapper_->DeviceScaleFactor(); - viewport_to_dip_scale_ = - client_->IsUseZoomForDSFEnabled() ? 1.0f / device_scale : 1.0f; + // An empty `window_rect` can be received here in the following cases: + // - If the embedded plugin size is 0. + // - If the embedded plugin size is not 0, it can come from re-layouts during + // the plugin initialization. + // For either case, there is no need to create a graphic device to display + // a PDF in an empty window. Since an empty `window_rect` can cause failure + // to create the graphic device, avoid all updates on the geometries and the + // device scales used by the plugin, the PaintManager and the PDFiumEngine + // unless a non-empty `window_rect` is received. + if (window_rect.IsEmpty()) + return; - // Note that `window_rect` is in viewport coordinates. It needs to be - // converted to DIPs before getting passed into - // PdfViewPluginBase::UpdateGeometryOnViewChanged(). - OnViewportChanged( - gfx::ScaleToEnclosingRectSafe(window_rect, viewport_to_dip_scale_), - device_scale); + OnViewportChanged(window_rect, container_wrapper_->DeviceScaleFactor()); } void PdfViewWebPlugin::UpdateFocus(bool focused, @@ -424,11 +440,11 @@ blink::WebInputEventResult PdfViewWebPlugin::HandleInputEvent( const blink::WebCoalescedInputEvent& event, ui::Cursor* cursor) { // TODO(crbug.com/702993): The input events received by the Pepper plugin - // already have the device scale applied. The scaling done here should be - // moved into `PdfViewPluginBase::HandleInputEvent()` once the Pepper plugin - // is removed. + // already have the viewport-to-DIP scale applied. The scaling done here + // should be moved into `PdfViewPluginBase::HandleInputEvent()` once the + // Pepper plugin is removed. std::unique_ptr scaled_event = - ui::ScaleWebInputEvent(event.Event(), device_scale()); + ui::ScaleWebInputEvent(event.Event(), viewport_to_dip_scale_); const blink::WebInputEvent& event_to_handle = scaled_event ? *scaled_event : event.Event(); @@ -556,20 +572,17 @@ bool PdfViewWebPlugin::StartFind(const blink::WebString& search_text, bool case_sensitive, int identifier) { find_identifier_ = identifier; - engine()->StartFind(search_text.Utf8(), case_sensitive); - return true; + return PdfViewPluginBase::StartFind(search_text.Utf8(), case_sensitive); } void PdfViewWebPlugin::SelectFindResult(bool forward, int identifier) { find_identifier_ = identifier; - engine()->SelectFindResult(forward); + PdfViewPluginBase::SelectFindResult(forward); } void PdfViewWebPlugin::StopFind() { find_identifier_ = -1; - engine()->StopFind(); - // TODO(crbug.com/1199999): Clear tickmarks on scroller when find is - // dismissed. + PdfViewPluginBase::StopFind(); } bool PdfViewWebPlugin::CanRotateView() { @@ -589,28 +602,42 @@ void PdfViewWebPlugin::RotateView(blink::WebPlugin::RotationType type) { } } +bool PdfViewWebPlugin::ShouldDispatchImeEventsToPlugin() { + return true; +} + blink::WebTextInputType PdfViewWebPlugin::GetPluginTextInputType() { return text_input_type_; } -void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) { - set_cursor_type(new_cursor_type); +gfx::Rect PdfViewWebPlugin::GetPluginCaretBounds() { + return caret_rect_; } -void PdfViewWebPlugin::UpdateTickMarks( - const std::vector& tickmarks) {} +void PdfViewWebPlugin::ImeSetCompositionForPlugin( + const blink::WebString& text, + const std::vector& /*ime_text_spans*/, + const gfx::Range& /*replacement_range*/, + int /*selection_start*/, + int /*selection_end*/) { + composition_text_ = text; +} -void PdfViewWebPlugin::NotifyNumberOfFindResultsChanged(int total, - bool final_result) { - // After stopping search and setting `find_identifier_` to -1 there still may - // be a NotifyNumberOfFindResultsChanged notification pending from engine. - // Just ignore them. - if (find_identifier_ == -1 || !container_wrapper_) - return; +void PdfViewWebPlugin::ImeCommitTextForPlugin( + const blink::WebString& text, + const std::vector& /*ime_text_spans*/, + const gfx::Range& /*replacement_range*/, + int /*relative_cursor_pos*/) { + HandleImeCommit(text); +} - container_wrapper_->ReportFindInPageMatchCount(find_identifier_, total, - final_result); - // TODO(crbug.com/1199999): Set tickmarks on scroller. +void PdfViewWebPlugin::ImeFinishComposingTextForPlugin( + bool /*keep_selection*/) { + HandleImeCommit(composition_text_); +} + +void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) { + set_cursor_type(new_cursor_type); } void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index) { @@ -622,6 +649,11 @@ void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index) { current_find_index + 1); } +void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) { + caret_rect_ = gfx::ScaleToEnclosingRectSafe( + caret_rect + available_area().OffsetFromOrigin(), device_to_css_scale_); +} + void PdfViewWebPlugin::Alert(const std::string& message) { container_wrapper_->Alert(blink::WebString::FromUTF8(message)); } @@ -638,10 +670,6 @@ std::string PdfViewWebPlugin::Prompt(const std::string& question, .Utf8(); } -void PdfViewWebPlugin::SubmitForm(const std::string& url, - const void* data, - int length) {} - std::vector PdfViewWebPlugin::SearchString(const char16_t* string, const char16_t* term, @@ -730,6 +758,10 @@ void PdfViewWebPlugin::UpdateSnapshot(sk_sp snapshot) { InvalidatePluginContainer(); } +void PdfViewWebPlugin::EnableAccessibility() { + PdfViewPluginBase::EnableAccessibility(); +} + void PdfViewWebPlugin::HandleAccessibilityAction( const AccessibilityActionData& action_data) { PdfViewPluginBase::HandleAccessibilityAction(action_data); @@ -745,17 +777,6 @@ std::unique_ptr PdfViewWebPlugin::CreateUrlLoaderInternal() { return loader; } -// Modeled on `OutOfProcessInstance::DidOpen()`. -void PdfViewWebPlugin::DidOpen(std::unique_ptr loader, - int32_t result) { - if (result == Result::kSuccess) { - if (!engine()->HandleDocumentLoad(std::move(loader), GetURL())) - DocumentLoadFailed(); - } else { - NOTIMPLEMENTED(); - } -} - void PdfViewWebPlugin::SendMessage(base::Value message) { post_message_sender_.Post(std::move(message)); } @@ -799,9 +820,36 @@ void PdfViewWebPlugin::SetAccessibilityPageInfo( void PdfViewWebPlugin::SetAccessibilityViewportInfo( const AccessibilityViewportInfo& viewport_info) { - if (!pdf_accessibility_data_handler_) + // The accessibility tree cannot be updated within the scope of + // `UpdateGeometry`. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&PdfViewWebPlugin::OnSetAccessibilityViewportInfo, + weak_factory_.GetWeakPtr(), viewport_info)); +} + +void PdfViewWebPlugin::NotifyFindResultsChanged(int total, bool final_result) { + // After stopping search and setting `find_identifier_` to -1 there still may + // be a NotifyNumberOfFindResultsChanged notification pending from engine. + // Just ignore them. + if (find_identifier_ == -1 || !container_wrapper_) return; - pdf_accessibility_data_handler_->SetAccessibilityViewportInfo(viewport_info); + + container_wrapper_->ReportFindInPageMatchCount(find_identifier_, total, + final_result); +} +void PdfViewWebPlugin::NotifyFindTickmarks( + const std::vector& tickmarks) { + auto* service = GetPdfService(); + if (!service) + return; + + if (!find_remote_) { + mojo::PendingRemote pending_find_remote; + service->GetPdfFindInPage(&pending_find_remote); + find_remote_.Bind(std::move(pending_find_remote)); + } + find_remote_->SetTickmarks(tickmarks); } void PdfViewWebPlugin::SetContentRestrictions(int content_restrictions) { @@ -864,21 +912,27 @@ void PdfViewWebPlugin::UserMetricsRecordAction(const std::string& action) { client_->RecordComputedAction(action); } -void PdfViewWebPlugin::OnViewportChanged(const gfx::Rect& view_rect, - float new_device_scale) { - UpdateGeometryOnViewChanged(view_rect, new_device_scale); - - if (IsPrintPreview() && !stop_scrolling()) { - DCHECK_EQ(new_device_scale, device_scale()); - gfx::ScrollOffset scroll_offset = - container_wrapper_->GetFrame()->GetScrollOffset(); - scroll_offset.Scale(device_scale()); - set_scroll_position(gfx::Point(scroll_offset.x(), scroll_offset.y())); - UpdateScroll(); +void PdfViewWebPlugin::OnViewportChanged( + const gfx::Rect& plugin_rect_in_css_pixel, + float new_device_scale) { + css_plugin_rect_ = plugin_rect_in_css_pixel; + float css_to_device_pixel_scale; + if (client_->IsUseZoomForDSFEnabled()) { + viewport_to_dip_scale_ = 1.0f / new_device_scale; + device_to_css_scale_ = 1.0f; + css_to_device_pixel_scale = 1.0f; + } else { + viewport_to_dip_scale_ = 1.0f; + device_to_css_scale_ = 1.0f / new_device_scale; + css_to_device_pixel_scale = new_device_scale; } - // Scrolling in the main PDF Viewer UI is already handled by - // `HandleUpdateScrollMessage()`. + // `plugin_rect_in_css_pixel` needs to be converted to device pixels before + // getting passed into PdfViewPluginBase::UpdateGeometryOnPluginRectChanged(). + UpdateGeometryOnPluginRectChanged( + gfx::ScaleToEnclosingRectSafe(plugin_rect_in_css_pixel, + css_to_device_pixel_scale), + new_device_scale); } void PdfViewWebPlugin::InvalidatePluginContainer() { @@ -925,10 +979,43 @@ bool PdfViewWebPlugin::Redo() { return true; } +void PdfViewWebPlugin::HandleImeCommit(const blink::WebString& text) { + if (text.IsEmpty()) + return; + + std::u16string text16 = text.Utf16(); + composition_text_.Reset(); + + size_t i = 0; + for (base::i18n::UTF16CharIterator iterator(text16); iterator.Advance();) { + blink::WebKeyboardEvent char_event(blink::WebInputEvent::Type::kChar, + blink::WebInputEvent::kNoModifiers, + ui::EventTimeForNow()); + char_event.windows_key_code = text16[i]; + char_event.native_key_code = text16[i]; + + for (const size_t char_start = i; i < iterator.array_pos(); ++i) { + char_event.text[i - char_start] = text16[i]; + char_event.unmodified_text[i - char_start] = text16[i]; + } + + blink::WebCoalescedInputEvent input_event(char_event, ui::LatencyInfo()); + ui::Cursor dummy_cursor_info; + HandleInputEvent(input_event, &dummy_cursor_info); + } +} + void PdfViewWebPlugin::OnInvokePrintDialog(int32_t /*result*/) { client_->Print(Container()->GetElement()); } +void PdfViewWebPlugin::OnSetAccessibilityViewportInfo( + const AccessibilityViewportInfo& viewport_info) { + if (!pdf_accessibility_data_handler_) + return; + pdf_accessibility_data_handler_->SetAccessibilityViewportInfo(viewport_info); +} + pdf::mojom::PdfService* PdfViewWebPlugin::GetPdfService() { return pdf_service_remote_.is_bound() ? pdf_service_remote_.get() : nullptr; } diff --git a/chromium/pdf/pdf_view_web_plugin.h b/chromium/pdf/pdf_view_web_plugin.h index 2ad39f80101..f0c182f948d 100644 --- a/chromium/pdf/pdf_view_web_plugin.h +++ b/chromium/pdf/pdf_view_web_plugin.h @@ -8,11 +8,13 @@ #include #include +#include #include #include "base/memory/weak_ptr.h" #include "cc/paint/paint_image.h" #include "mojo/public/cpp/bindings/associated_remote.h" +#include "mojo/public/cpp/bindings/remote.h" #include "pdf/mojom/pdf.mojom.h" #include "pdf/pdf_accessibility_action_handler.h" #include "pdf/pdf_view_plugin_base.h" @@ -23,6 +25,7 @@ #include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_text_input_type.h" #include "third_party/blink/public/web/web_plugin.h" +#include "third_party/blink/public/web/web_plugin_container.h" #include "third_party/blink/public/web/web_plugin_params.h" #include "v8/include/v8.h" @@ -31,7 +34,6 @@ class WebAssociatedURLLoader; class WebElement; class WebLocalFrame; class WebLocalFrameClient; -class WebPluginContainer; class WebURL; class WebURLRequest; struct WebAssociatedURLLoaderOptions; @@ -47,6 +49,7 @@ class MetafileSkia; namespace chrome_pdf { +class PDFiumEngine; class PdfAccessibilityDataHandler; // Skeleton for a `blink::WebPlugin` to replace `OutOfProcessInstance`. @@ -65,6 +68,10 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, // page in it. virtual void Invalidate() = 0; + // Notifies the container about which touch events the plugin accepts. + virtual void RequestTouchEventType( + blink::WebPluginContainer::TouchEventRequestType request_type) = 0; + // Notify the web plugin container about the total matches of a find // request. virtual void ReportFindInPageMatchCount(int identifier, @@ -107,6 +114,9 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, // Notifies the frame widget about the selection bound change. virtual void UpdateSelectionBounds() = 0; + // Gets the embedder's origin as a serialized string. + virtual std::string GetEmbedderOriginString() = 0; + // Returns the local frame to which the web plugin container belongs. virtual blink::WebLocalFrame* GetFrame() = 0; @@ -193,20 +203,31 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, void StopFind() override; bool CanRotateView() override; void RotateView(blink::WebPlugin::RotationType type) override; + + bool ShouldDispatchImeEventsToPlugin() override; blink::WebTextInputType GetPluginTextInputType() override; + gfx::Rect GetPluginCaretBounds() override; + void ImeSetCompositionForPlugin( + const blink::WebString& text, + const std::vector& ime_text_spans, + const gfx::Range& replacement_range, + int selection_start, + int selection_end) override; + void ImeCommitTextForPlugin( + const blink::WebString& text, + const std::vector& ime_text_spans, + const gfx::Range& replacement_range, + int relative_cursor_pos) override; + void ImeFinishComposingTextForPlugin(bool keep_selection) override; // PdfViewPluginBase: void UpdateCursor(ui::mojom::CursorType new_cursor_type) override; - void UpdateTickMarks(const std::vector& tickmarks) override; - void NotifyNumberOfFindResultsChanged(int total, bool final_result) override; void NotifySelectedFindResultChanged(int current_find_index) override; + void CaretChanged(const gfx::Rect& caret_rect) override; void Alert(const std::string& message) override; bool Confirm(const std::string& message) override; std::string Prompt(const std::string& question, const std::string& default_answer) override; - void SubmitForm(const std::string& url, - const void* data, - int length) override; std::vector SearchString(const char16_t* string, const char16_t* term, bool case_sensitive) override; @@ -235,20 +256,23 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, void UpdateSnapshot(sk_sp snapshot) override; // PdfAccessibilityActionHandler: + void EnableAccessibility() override; void HandleAccessibilityAction( const AccessibilityActionData& action_data) override; - // Initializes the plugin using the `container_wrapper` provided by tests. - bool InitializeForTesting( - std::unique_ptr container_wrapper); + // Initializes the plugin using the `container_wrapper` and `engine` provided + // by tests. + bool InitializeForTesting(std::unique_ptr container_wrapper, + std::unique_ptr engine); const gfx::Rect& GetPluginRectForTesting() const { return plugin_rect(); } + float GetDeviceScaleForTesting() const { return device_scale(); } + protected: // PdfViewPluginBase: base::WeakPtr GetWeakPtr() override; std::unique_ptr CreateUrlLoaderInternal() override; - void DidOpen(std::unique_ptr loader, int32_t result) override; void SendMessage(base::Value message) override; void SaveAs() override; void InitImageData(const gfx::Size& size) override; @@ -260,6 +284,8 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, AccessibilityPageObjects page_objects) override; void SetAccessibilityViewportInfo( const AccessibilityViewportInfo& viewport_info) override; + void NotifyFindResultsChanged(int total, bool final_result) override; + void NotifyFindTickmarks(const std::vector& tickmarks) override; void SetContentRestrictions(int content_restrictions) override; void SetPluginCanSave(bool can_save) override; void PluginDidStartLoading() override; @@ -276,9 +302,11 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, // Call `Destroy()` instead. ~PdfViewWebPlugin() override; - bool InitializeCommon(std::unique_ptr container_wrapper); + bool InitializeCommon(std::unique_ptr container_wrapper, + std::unique_ptr engine); - void OnViewportChanged(const gfx::Rect& view_rect, float new_device_scale); + void OnViewportChanged(const gfx::Rect& plugin_rect_in_css_pixel, + float new_device_scale); // Invalidates the entire web plugin container and schedules a paint of the // page in it. @@ -291,6 +319,10 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, bool Undo(); bool Redo(); + // Helper method for converting IME text to input events. + // TODO(crbug.com/1253665): Consider handling composition events. + void HandleImeCommit(const blink::WebString& text); + // Callback to print without re-entrancy issues. The callback prevents the // invocation of printing in the middle of an event handler, which is risky; // see crbug.com/66334. @@ -298,6 +330,11 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, // the plugin are moved off the main thread. void OnInvokePrintDialog(int32_t /*result*/); + // Callback to set the viewport information in accessibility tree + // asynchronously. + void OnSetAccessibilityViewportInfo( + const AccessibilityViewportInfo& viewport_info); + // May be null in unit tests. pdf::mojom::PdfService* GetPdfService(); @@ -305,9 +342,13 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, std::unique_ptr const client_; + // Used to access the services provided by the browser. // May be unbound in unit tests. mojo::AssociatedRemote const pdf_service_remote_; + // Used to access find-in-page interface provided by the PDF extension. + mojo::Remote find_remote_; + // The id of the current find operation, or -1 if no current operation is // present. int find_identifier_ = -1; @@ -315,6 +356,10 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, blink::WebTextInputType text_input_type_ = blink::WebTextInputType::kWebTextInputTypeNone; + gfx::Rect caret_rect_; + + blink::WebString composition_text_; + // Whether the plugin element currently has focus. bool has_focus_ = false; @@ -330,6 +375,12 @@ class PdfViewWebPlugin final : public PdfViewPluginBase, // The viewport coordinates to DIP (device-independent pixel) ratio. float viewport_to_dip_scale_ = 1.0f; + // The device pixel to CSS pixel ratio. + float device_to_css_scale_ = 1.0f; + + // The plugin rect in CSS pixels. + gfx::Rect css_plugin_rect_; + // May be null in unit tests. std::unique_ptr const pdf_accessibility_data_handler_; diff --git a/chromium/pdf/pdf_view_web_plugin_unittest.cc b/chromium/pdf/pdf_view_web_plugin_unittest.cc index b5046a32119..0d39071a88a 100644 --- a/chromium/pdf/pdf_view_web_plugin_unittest.cc +++ b/chromium/pdf/pdf_view_web_plugin_unittest.cc @@ -5,27 +5,42 @@ #include "pdf/pdf_view_web_plugin.h" #include +#include #include +#include "base/strings/string_piece.h" #include "cc/paint/paint_canvas.h" #include "cc/test/pixel_comparator.h" #include "cc/test/pixel_test_utils.h" #include "pdf/ppapi_migration/bitmap.h" #include "pdf/test/test_helpers.h" +#include "pdf/test/test_pdfium_engine.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/input/web_coalesced_input_event.h" +#include "third_party/blink/public/common/input/web_input_event.h" +#include "third_party/blink/public/common/input/web_keyboard_event.h" +#include "third_party/blink/public/common/input/web_mouse_event.h" +#include "third_party/blink/public/common/input/web_pointer_properties.h" #include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_text_input_type.h" #include "third_party/blink/public/platform/web_vector.h" #include "third_party/blink/public/web/web_associated_url_loader.h" +#include "third_party/blink/public/web/web_plugin_container.h" #include "third_party/blink/public/web/web_plugin_params.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColor.h" +#include "ui/base/cursor/cursor.h" +#include "ui/events/blink/blink_event_util.h" +#include "ui/events/keycodes/dom/dom_code.h" +#include "ui/events/keycodes/dom/dom_key.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/point_f.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/skia_util.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/latency/latency_info.h" namespace chrome_pdf { @@ -42,6 +57,9 @@ using ::testing::Return; // testing. constexpr gfx::Size kCanvasSize(100, 100); +// A common device scale for high DPI displays. +constexpr float kDeviceScale = 2.0f; + // Note: Make sure `kDefaultColor` is different from `kPaintColor` and the // plugin's background color. This will help identify bitmap changes after // painting. @@ -53,11 +71,15 @@ struct PaintParams { // The plugin container's device scale. float device_scale; - // The window area. + // The window area in CSS pixels. gfx::Rect window_rect; - // The target painting area on the canvas. + // The target painting area on the canvas in CSS pixels. gfx::Rect paint_rect; + + // The expected clipped area to be filled with paint color. The clipped area + // should be the intersection of `paint_rect` and `window_rect`. + gfx::Rect expected_clipped_rect; }; MATCHER(SearchStringResultEq, "") { @@ -66,26 +88,44 @@ MATCHER(SearchStringResultEq, "") { return l.start_index == r.start_index && l.length == r.length; } +MATCHER_P(IsExpectedImeKeyEvent, expected_text, "") { + if (arg.GetType() != blink::WebInputEvent::Type::kChar) + return false; + + const auto& event = static_cast(arg); + return event.GetModifiers() == blink::WebInputEvent::kNoModifiers && + event.windows_key_code == expected_text[0] && + event.native_key_code == expected_text[0] && + event.dom_code == static_cast(ui::DomCode::NONE) && + event.dom_key == ui::DomKey::NONE && !event.is_system_key && + !event.is_browser_shortcut && event.text == expected_text && + event.unmodified_text == expected_text; +} + // Generates the expected `SkBitmap` with `paint_color` filled in the expected // clipped area and `kDefaultColor` as the background color. -SkBitmap GenerateExpectedBitmapForPaint(float device_scale, - bool use_zoom_for_dsf, - const gfx::Rect& plugin_rect, - const gfx::Rect& paint_rect, +SkBitmap GenerateExpectedBitmapForPaint(const gfx::Rect& expected_clipped_rect, SkColor paint_color) { - float inverse_scale = use_zoom_for_dsf ? 1.0f : 1.0f / device_scale; - // TODO(crbug.com/1238395): Improve the test by pre-define the - // `expected_clipped_area` instead of calculating it inside the test. - gfx::Rect expected_clipped_area = gfx::IntersectRects( - gfx::ScaleToEnclosingRectSafe(plugin_rect, inverse_scale), paint_rect); SkBitmap expected_bitmap = CreateN32PremulSkBitmap(gfx::SizeToSkISize(kCanvasSize)); expected_bitmap.eraseColor(kDefaultColor); - expected_bitmap.erase(paint_color, gfx::RectToSkIRect(expected_clipped_area)); + expected_bitmap.erase(paint_color, gfx::RectToSkIRect(expected_clipped_rect)); return expected_bitmap; } -class FakeContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper { +blink::WebMouseEvent CreateDefaultMouseDownEvent() { + blink::WebMouseEvent web_event( + blink::WebInputEvent::Type::kMouseDown, + /*position=*/gfx::PointF(), + /*global_position=*/gfx::PointF(), + blink::WebPointerProperties::Button::kLeft, + /*click_count_param=*/1, blink::WebInputEvent::Modifiers::kLeftButtonDown, + blink::WebInputEvent::GetStaticTimeStampForTests()); + web_event.SetFrameScale(1); + return web_event; +} + +class FakeContainerWrapper : public PdfViewWebPlugin::ContainerWrapper { public: explicit FakeContainerWrapper(PdfViewWebPlugin* web_plugin) : web_plugin_(web_plugin) { @@ -103,6 +143,11 @@ class FakeContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper { // PdfViewWebPlugin::ContainerWrapper: void Invalidate() override {} + MOCK_METHOD(void, + RequestTouchEventType, + (blink::WebPluginContainer::TouchEventRequestType), + (override)); + MOCK_METHOD(void, ReportFindInPageMatchCount, (int, int, bool), (override)); MOCK_METHOD(void, ReportFindInPageSelection, (int, int), (override)); @@ -137,6 +182,10 @@ class FakeContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper { MOCK_METHOD(void, UpdateSelectionBounds, (), (override)); + std::string GetEmbedderOriginString() override { + return "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/"; + } + blink::WebLocalFrame* GetFrame() override { return nullptr; } blink::WebLocalFrameClient* GetWebLocalFrameClient() override { @@ -182,18 +231,22 @@ class FakePdfViewWebPluginClient : public PdfViewWebPlugin::Client { } // namespace -class PdfViewWebPluginTest : public testing::Test { +class PdfViewWebPluginWithoutInitializeTest : public testing::Test { public: + PdfViewWebPluginWithoutInitializeTest( + const PdfViewWebPluginWithoutInitializeTest&) = delete; + PdfViewWebPluginWithoutInitializeTest& operator=( + const PdfViewWebPluginWithoutInitializeTest&) = delete; + + protected: // Custom deleter for `plugin_`. PdfViewWebPlugin must be destroyed by // PdfViewWebPlugin::Destroy() instead of its destructor. struct PluginDeleter { void operator()(PdfViewWebPlugin* ptr) { ptr->Destroy(); } }; - PdfViewWebPluginTest() = default; - PdfViewWebPluginTest(const PdfViewWebPluginTest&) = delete; - PdfViewWebPluginTest& operator=(const PdfViewWebPluginTest&) = delete; - ~PdfViewWebPluginTest() override = default; + PdfViewWebPluginWithoutInitializeTest() = default; + ~PdfViewWebPluginWithoutInitializeTest() override = default; void SetUp() override { // Set a dummy URL for initializing the plugin. @@ -208,15 +261,37 @@ class PdfViewWebPluginTest : public testing::Test { plugin_ = std::unique_ptr(new PdfViewWebPlugin( std::move(client), std::move(unbound_remote), params)); + } + + void TearDown() override { plugin_.reset(); } - auto wrapper = std::make_unique(plugin_.get()); + FakePdfViewWebPluginClient* client_ptr_; + std::unique_ptr plugin_; +}; + +class PdfViewWebPluginTest : public PdfViewWebPluginWithoutInitializeTest { + protected: + void SetUp() override { + PdfViewWebPluginWithoutInitializeTest::SetUp(); + + auto wrapper = + std::make_unique>(plugin_.get()); wrapper_ptr_ = wrapper.get(); - plugin_->InitializeForTesting(std::move(wrapper)); + auto engine = CreateEngine(); + engine_ptr_ = engine.get(); + EXPECT_TRUE( + plugin_->InitializeForTesting(std::move(wrapper), std::move(engine))); } void TearDown() override { - plugin_.reset(); wrapper_ptr_ = nullptr; + + PdfViewWebPluginWithoutInitializeTest::TearDown(); + } + + // Allow derived test classes to create their own custom TestPDFiumEngine. + virtual std::unique_ptr CreateEngine() { + return std::make_unique(plugin_.get()); } void UpdatePluginGeometry(float device_scale, const gfx::Rect& window_rect) { @@ -230,19 +305,21 @@ class PdfViewWebPluginTest : public testing::Test { void TestUpdateGeometrySetsPluginRect(float device_scale, const gfx::Rect& window_rect, + float expected_device_scale, const gfx::Rect& expected_plugin_rect) { UpdatePluginGeometry(device_scale, window_rect); + EXPECT_EQ(expected_device_scale, plugin_->GetDeviceScaleForTesting()) + << "Device scale comparison failure at device scale of " + << device_scale; EXPECT_EQ(expected_plugin_rect, plugin_->GetPluginRectForTesting()) - << "Failure at device scale of " << device_scale << ", window rect of " - << window_rect.ToString(); + << "Plugin rect comparison failure at device scale of " << device_scale + << ", window rect of " << window_rect.ToString(); } void TestPaintEmptySnapshots(float device_scale, - bool use_zoom_for_dsf, const gfx::Rect& window_rect, - const gfx::Rect& paint_rect) { - EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) - .WillOnce(Return(use_zoom_for_dsf)); + const gfx::Rect& paint_rect, + const gfx::Rect& expected_clipped_rect) { UpdatePluginGeometry(device_scale, window_rect); canvas_.DrawColor(kDefaultColor); @@ -251,8 +328,7 @@ class PdfViewWebPluginTest : public testing::Test { // Expect the clipped area on canvas to be filled with plugin's background // color. SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint( - device_scale, use_zoom_for_dsf, plugin_->GetPluginRectForTesting(), - paint_rect, plugin_->GetBackgroundColor()); + expected_clipped_rect, plugin_->GetBackgroundColor()); EXPECT_TRUE( cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap, cc::ExactPixelComparator(/*discard_alpha=*/false))) @@ -261,11 +337,9 @@ class PdfViewWebPluginTest : public testing::Test { } void TestPaintSnapshots(float device_scale, - bool use_zoom_for_dsf, const gfx::Rect& window_rect, - const gfx::Rect& paint_rect) { - EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) - .WillOnce(Return(use_zoom_for_dsf)); + const gfx::Rect& paint_rect, + const gfx::Rect& expected_clipped_rect) { UpdatePluginGeometry(device_scale, window_rect); canvas_.DrawColor(kDefaultColor); @@ -282,8 +356,8 @@ class PdfViewWebPluginTest : public testing::Test { plugin_->Paint(canvas_.sk_canvas(), paint_rect); // Expect the clipped area on canvas to be filled with `kPaintColor`. - SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint( - device_scale, use_zoom_for_dsf, plugin_rect, paint_rect, kPaintColor); + SkBitmap expected_bitmap = + GenerateExpectedBitmapForPaint(expected_clipped_rect, kPaintColor); EXPECT_TRUE( cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap, cc::ExactPixelComparator(/*discard_alpha=*/false))) @@ -291,30 +365,44 @@ class PdfViewWebPluginTest : public testing::Test { << window_rect.ToString(); } + TestPDFiumEngine* engine_ptr_; FakeContainerWrapper* wrapper_ptr_; - FakePdfViewWebPluginClient* client_ptr_; - std::unique_ptr plugin_; // Provides the cc::PaintCanvas for painting. gfx::Canvas canvas_{kCanvasSize, /*image_scale=*/1.0f, /*is_opaque=*/true}; }; -// TODO(crbug.com/1238395): Split this test into two: One with zoom-for-DSF -// enabled, one with zoom-for-DSF disabled. -TEST_F(PdfViewWebPluginTest, - UpdateGeometrySetsPluginRectOnDifferentUseZoomForDSFSettings) { - // Use the same device scale for this test. - const float device_scale = 2.0f; +TEST_F(PdfViewWebPluginWithoutInitializeTest, Initialize) { + auto wrapper = + std::make_unique>(plugin_.get()); + auto engine = std::make_unique(plugin_.get()); + EXPECT_CALL(*wrapper, + RequestTouchEventType( + blink::WebPluginContainer::kTouchEventRequestTypeRaw)); - // Test when using zoom for DSF is enabled. - EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled).WillOnce(Return(true)); - TestUpdateGeometrySetsPluginRect(device_scale, gfx::Rect(4, 4, 12, 12), - gfx::Rect(4, 4, 12, 12)); + EXPECT_TRUE( + plugin_->InitializeForTesting(std::move(wrapper), std::move(engine))); +} - // Test when using zoom for DSF is disabled. - EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled).WillOnce(Return(false)); - TestUpdateGeometrySetsPluginRect(device_scale, gfx::Rect(4, 4, 12, 12), - gfx::Rect(8, 8, 24, 24)); +TEST_F(PdfViewWebPluginTest, UpdateGeometrySetsPluginRectUseZoomForDSFEnabled) { + EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f)); + TestUpdateGeometrySetsPluginRect( + /*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(4, 4, 12, 12), + /*expected_device_scale=*/2.0f, + /*expected_plugin_rect=*/gfx::Rect(4, 4, 12, 12)); +} + +TEST_F(PdfViewWebPluginTest, + UpdateGeometrySetsPluginRectUseZoomForDSFDisabled) { + EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f)); + TestUpdateGeometrySetsPluginRect( + /*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(4, 4, 12, 12), + /*expected_device_scale=*/2.0f, + /*expected_plugin_rect=*/gfx::Rect(8, 8, 24, 24)); } TEST_F(PdfViewWebPluginTest, @@ -323,9 +411,12 @@ TEST_F(PdfViewWebPluginTest, // The plugin container's device scale. float device_scale; - // The window rect in device pixels. + // The window rect in CSS pixels. gfx::Rect window_rect; + // The expected plugin device scale. + float expected_device_scale; + // The expected plugin rect in device pixels. gfx::Rect expected_plugin_rect; }; @@ -335,51 +426,261 @@ TEST_F(PdfViewWebPluginTest, .WillRepeatedly(Return(true)); static constexpr UpdateGeometryParams kUpdateGeometryParams[] = { - {1.0f, gfx::Rect(3, 4, 5, 6), gfx::Rect(3, 4, 5, 6)}, - {2.0f, gfx::Rect(4, 4, 12, 12), gfx::Rect(4, 4, 12, 12)}, - {2.0f, gfx::Rect(5, 6, 7, 8), gfx::Rect(4, 6, 8, 8)}, + {1.0f, gfx::Rect(3, 4, 5, 6), 1.0f, gfx::Rect(3, 4, 5, 6)}, + {2.0f, gfx::Rect(3, 4, 5, 6), 2.0f, gfx::Rect(3, 4, 5, 6)}, }; for (const auto& params : kUpdateGeometryParams) { TestUpdateGeometrySetsPluginRect(params.device_scale, params.window_rect, + params.expected_device_scale, params.expected_plugin_rect); } } -TEST_F(PdfViewWebPluginTest, PaintEmptySnapshots) { - static constexpr PaintParams kPaintEmptySnapshotsParams[]{ - // The window origin falls outside the `paint_rect` area. - {1.0f, gfx::Rect(10, 10, 20, 20), gfx::Rect(5, 5, 15, 15)}, - {4.0f, gfx::Rect(10, 10, 20, 20), gfx::Rect(5, 5, 15, 15)}, - // The window origin falls within the `paint_rect` area. - {1.0f, gfx::Rect(4, 4, 20, 20), gfx::Rect(8, 8, 15, 15)}, - {4.0f, gfx::Rect(4, 4, 20, 20), gfx::Rect(8, 8, 15, 15)}, +class PdfViewWebPluginTestUseZoomForDSF + : public PdfViewWebPluginTest, + public testing::WithParamInterface { + public: + void SetUp() override { + PdfViewWebPluginTest::SetUp(); + ON_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillByDefault(Return(GetParam())); + } +}; + +TEST_P(PdfViewWebPluginTestUseZoomForDSF, + UpdateGeometrySetsPluginRectWithEmptyWindow) { + EXPECT_CALL(*engine_ptr_, ZoomUpdated).Times(0); + TestUpdateGeometrySetsPluginRect( + /*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(2, 2, 0, 0), + /*expected_device_scale=*/1.0f, /*expected_plugin_rect=*/gfx::Rect()); +} + +TEST_P(PdfViewWebPluginTestUseZoomForDSF, PaintEmptySnapshots) { + TestPaintEmptySnapshots(/*device_scale=*/4.0f, + /*window_rect=*/gfx::Rect(10, 10, 20, 20), + /*paint_rect=*/gfx::Rect(5, 5, 15, 15), + /*expected_clipped_rect=*/gfx::Rect(10, 10, 10, 10)); +} + +TEST_P(PdfViewWebPluginTestUseZoomForDSF, PaintSnapshots) { + TestPaintSnapshots(/*device_scale=*/4.0f, + /*window_rect=*/gfx::Rect(10, 10, 20, 20), + /*paint_rect=*/gfx::Rect(5, 5, 15, 15), + /*expected_clipped_rect=*/gfx::Rect(10, 10, 10, 10)); +} + +TEST_P(PdfViewWebPluginTestUseZoomForDSF, + PaintSnapshotsWithVariousDeviceScales) { + static constexpr PaintParams kPaintWithVariousScalesParams[] = { + {0.4f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30), + gfx::Rect(10, 10, 28, 28)}, + {1.0f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30), + gfx::Rect(10, 10, 28, 28)}, + {4.0f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30), + gfx::Rect(10, 10, 28, 28)}, }; - for (const auto& params : kPaintEmptySnapshotsParams) { - TestPaintEmptySnapshots(params.device_scale, /*use_zoom_for_dsf=*/true, - params.window_rect, params.paint_rect); - TestPaintEmptySnapshots(params.device_scale, /*use_zoom_for_dsf=*/false, - params.window_rect, params.paint_rect); + for (const auto& params : kPaintWithVariousScalesParams) { + TestPaintSnapshots(params.device_scale, params.window_rect, + params.paint_rect, params.expected_clipped_rect); } } -TEST_F(PdfViewWebPluginTest, PaintSnapshots) { - static constexpr PaintParams kPaintWithScalesTestParams[] = { +TEST_P(PdfViewWebPluginTestUseZoomForDSF, + PaintSnapshotsWithVariousRectPositions) { + static constexpr PaintParams kPaintWithVariousPositionsParams[] = { // The window origin falls outside the `paint_rect` area. - {1.0f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30)}, - {2.0f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30)}, + {4.0f, gfx::Rect(10, 10, 20, 20), gfx::Rect(5, 5, 15, 15), + gfx::Rect(10, 10, 10, 10)}, // The window origin falls within the `paint_rect` area. - {1.0f, gfx::Rect(10, 10, 30, 30), gfx::Rect(4, 4, 30, 30)}, - {2.0f, gfx::Rect(10, 10, 30, 30), gfx::Rect(4, 4, 30, 30)}, + {4.0f, gfx::Rect(4, 4, 20, 20), gfx::Rect(8, 8, 15, 15), + gfx::Rect(8, 8, 15, 15)}, + }; + + for (const auto& params : kPaintWithVariousPositionsParams) { + TestPaintSnapshots(params.device_scale, params.window_rect, + params.paint_rect, params.expected_clipped_rect); + } +} + +INSTANTIATE_TEST_SUITE_P(All, + PdfViewWebPluginTestUseZoomForDSF, + testing::Bool()); + +class PdfViewWebPluginMouseEventsTest : public PdfViewWebPluginTest { + public: + class TestPDFiumEngineForMouseEvents : public TestPDFiumEngine { + public: + explicit TestPDFiumEngineForMouseEvents(PDFEngine::Client* client) + : TestPDFiumEngine(client) {} + + // TestPDFiumEngine: + bool HandleInputEvent(const blink::WebInputEvent& event) override { + // Since blink::WebInputEvent is an abstract class, we cannot use equal + // matcher to verify its value. Here we test with blink::WebMouseEvent + // specifically. + if (!blink::WebInputEvent::IsMouseEventType(event.GetType())) + return false; + + scaled_mouse_event_ = std::make_unique(); + *scaled_mouse_event_ = static_cast(event); + return true; + } + + const blink::WebMouseEvent* GetScaledMouseEvent() const { + return scaled_mouse_event_.get(); + } + + private: + std::unique_ptr scaled_mouse_event_; }; - for (const auto& params : kPaintWithScalesTestParams) { - TestPaintSnapshots(params.device_scale, /*use_zoom_for_dsf=*/true, - params.window_rect, params.paint_rect); - TestPaintSnapshots(params.device_scale, /*use_zoom_for_dsf=*/false, - params.window_rect, params.paint_rect); + std::unique_ptr CreateEngine() override { + return std::make_unique(plugin_.get()); + } + + TestPDFiumEngineForMouseEvents* engine() { + return static_cast(engine_ptr_); + } +}; + +TEST_F(PdfViewWebPluginMouseEventsTest, + HandleInputEventWithUseZoomForDSFEnabled) { + // Test when using zoom for DSF is enabled. + EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillRepeatedly(Return(true)); + wrapper_ptr_->set_device_scale(kDeviceScale); + UpdatePluginGeometry(kDeviceScale, gfx::Rect(20, 20)); + + ui::Cursor dummy_cursor; + plugin_->HandleInputEvent( + blink::WebCoalescedInputEvent(CreateDefaultMouseDownEvent(), + ui::LatencyInfo()), + &dummy_cursor); + + const blink::WebMouseEvent* event = engine()->GetScaledMouseEvent(); + ASSERT_TRUE(event); + EXPECT_EQ(gfx::PointF(-10.0f, 0.0f), event->PositionInWidget()); +} + +TEST_F(PdfViewWebPluginMouseEventsTest, + HandleInputEventWithUseZoomForDSFDisabled) { + // Test when using zoom for DSF is disabled. + EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillRepeatedly(Return(false)); + wrapper_ptr_->set_device_scale(kDeviceScale); + UpdatePluginGeometry(kDeviceScale, gfx::Rect(20, 20)); + + ui::Cursor dummy_cursor; + plugin_->HandleInputEvent( + blink::WebCoalescedInputEvent(CreateDefaultMouseDownEvent(), + ui::LatencyInfo()), + &dummy_cursor); + + const blink::WebMouseEvent* event = engine()->GetScaledMouseEvent(); + ASSERT_TRUE(event); + EXPECT_EQ(gfx::PointF(-20.0f, 0.0f), event->PositionInWidget()); +} + +class PdfViewWebPluginImeTest : public PdfViewWebPluginTest { + public: + class TestPDFiumEngineForIme : public TestPDFiumEngine { + public: + explicit TestPDFiumEngineForIme(PDFEngine::Client* client) + : TestPDFiumEngine(client) {} + + // TestPDFiumEngine: + MOCK_METHOD(bool, + HandleInputEvent, + (const blink::WebInputEvent&), + (override)); + }; + + std::unique_ptr CreateEngine() override { + return std::make_unique(plugin_.get()); + } + + TestPDFiumEngineForIme* engine() { + return static_cast(engine_ptr_); + } + + void TestImeSetCompositionForPlugin(const blink::WebString& text) { + EXPECT_CALL(*engine(), HandleInputEvent).Times(0); + plugin_->ImeSetCompositionForPlugin(text, std::vector(), + gfx::Range(), + /*selection_start=*/0, + /*selection_end=*/0); } + + void TestImeFinishComposingTextForPlugin( + const blink::WebString& expected_text) { + InSequence sequence; + std::u16string expected_text16 = expected_text.Utf16(); + if (expected_text16.size()) { + for (const auto& c : expected_text16) { + base::StringPiece16 expected_key(&c, 1); + EXPECT_CALL(*engine(), + HandleInputEvent(IsExpectedImeKeyEvent(expected_key))) + .WillOnce(Return(true)); + } + } else { + EXPECT_CALL(*engine(), HandleInputEvent).Times(0); + } + plugin_->ImeFinishComposingTextForPlugin(false); + } + + void TestImeCommitTextForPlugin(const blink::WebString& text) { + InSequence sequence; + std::u16string expected_text16 = text.Utf16(); + if (expected_text16.size()) { + for (const auto& c : expected_text16) { + base::StringPiece16 event(&c, 1); + EXPECT_CALL(*engine(), HandleInputEvent(IsExpectedImeKeyEvent(event))) + .WillOnce(Return(true)); + } + } else { + EXPECT_CALL(*engine(), HandleInputEvent).Times(0); + } + plugin_->ImeCommitTextForPlugin(text, std::vector(), + gfx::Range(), + /*relative_cursor_pos=*/0); + } +}; + +TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishAscii) { + const blink::WebString text = blink::WebString::FromASCII("input"); + TestImeSetCompositionForPlugin(text); + TestImeFinishComposingTextForPlugin(text); +} + +TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishUnicode) { + const blink::WebString text = blink::WebString::FromUTF16(u"你好"); + TestImeSetCompositionForPlugin(text); + TestImeFinishComposingTextForPlugin(text); + // Calling ImeFinishComposingTextForPlugin() again is a no-op. + TestImeFinishComposingTextForPlugin(""); +} + +TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishEmpty) { + const blink::WebString text; + TestImeSetCompositionForPlugin(text); + TestImeFinishComposingTextForPlugin(text); +} + +TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginAscii) { + const blink::WebString text = blink::WebString::FromASCII("a b"); + TestImeCommitTextForPlugin(text); +} + +TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginUnicode) { + const blink::WebString text = blink::WebString::FromUTF16(u"さようなら"); + TestImeCommitTextForPlugin(text); +} + +TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginEmpty) { + const blink::WebString text; + TestImeCommitTextForPlugin(text); } TEST_F(PdfViewWebPluginTest, ChangeTextSelection) { @@ -492,4 +793,28 @@ TEST_F(PdfViewWebPluginTest, UpdateFocus) { plugin_->UpdateFocus(/*focused=*/true, blink::mojom::FocusType::kNone); } +TEST_F(PdfViewWebPluginTest, ShouldDispatchImeEventsToPlugin) { + ASSERT_TRUE(plugin_->ShouldDispatchImeEventsToPlugin()); +} + +TEST_F(PdfViewWebPluginTest, CaretChangeUseZoomForDSFEnabled) { + EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f)); + UpdatePluginGeometry( + /*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(12, 24, 36, 48)); + plugin_->CaretChanged(gfx::Rect(10, 20, 30, 40)); + EXPECT_EQ(gfx::Rect(28, 20, 30, 40), plugin_->GetPluginCaretBounds()); +} + +TEST_F(PdfViewWebPluginTest, CaretChangeUseZoomForDSFDisabled) { + EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f)); + UpdatePluginGeometry( + /*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(12, 24, 36, 48)); + plugin_->CaretChanged(gfx::Rect(10, 20, 30, 40)); + EXPECT_EQ(gfx::Rect(23, 10, 15, 20), plugin_->GetPluginCaretBounds()); +} + } // namespace chrome_pdf diff --git a/chromium/pdf/pdfium/DEPS b/chromium/pdf/pdfium/DEPS index 248ff0baeea..cdf6a23128c 100644 --- a/chromium/pdf/pdfium/DEPS +++ b/chromium/pdf/pdfium/DEPS @@ -2,5 +2,4 @@ include_rules = [ "+components/services/font/public/cpp", "+third_party/pdfium/public", "+ui/gfx/codec", - "+v8/include/cppgc/platform.h", ] diff --git a/chromium/pdf/pdfium/pdfium_assert_matching_enums.cc b/chromium/pdf/pdfium/pdfium_assert_matching_enums.cc index fc24ccd4ff5..44e916afe3d 100644 --- a/chromium/pdf/pdfium/pdfium_assert_matching_enums.cc +++ b/chromium/pdf/pdfium/pdfium_assert_matching_enums.cc @@ -264,6 +264,8 @@ STATIC_ASSERT_ENUM(kPostScript2, FPDF_PRINTMODE_POSTSCRIPT2); STATIC_ASSERT_ENUM(kPostScript3, FPDF_PRINTMODE_POSTSCRIPT3); STATIC_ASSERT_ENUM(kEmfWithReducedRasterization, FPDF_PRINTMODE_EMF_IMAGE_MASKS); +STATIC_ASSERT_ENUM(kPostScript3WithType42Fonts, + FPDF_PRINTMODE_POSTSCRIPT3_TYPE42); #endif } // namespace chrome_pdf diff --git a/chromium/pdf/pdfium/pdfium_engine.cc b/chromium/pdf/pdfium/pdfium_engine.cc index 341b183948b..5bdc6bb716e 100644 --- a/chromium/pdf/pdfium/pdfium_engine.cc +++ b/chromium/pdf/pdfium/pdfium_engine.cc @@ -25,6 +25,7 @@ #include "base/location.h" #include "base/notreached.h" #include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -108,8 +109,7 @@ constexpr int32_t kFormHighlightAlpha = 100; constexpr int kMaxPasswordTries = 3; -constexpr base::TimeDelta kTouchLongPressTimeout = - base::TimeDelta::FromMilliseconds(300); +constexpr base::TimeDelta kTouchLongPressTimeout = base::Milliseconds(300); // Windows has native panning capabilities. No need to use our own. #if defined(OS_WIN) @@ -122,8 +122,7 @@ constexpr int32_t kLoadingTextVerticalOffset = 50; // The maximum amount of time we'll spend doing a paint before we give back // control of the thread. -constexpr base::TimeDelta kMaxProgressivePaintTime = - base::TimeDelta::FromMilliseconds(300); +constexpr base::TimeDelta kMaxProgressivePaintTime = base::Milliseconds(300); // The maximum amount of time we'll spend doing the first paint. This is less // than the above to keep things smooth if the user is scrolling quickly. This @@ -137,7 +136,7 @@ constexpr base::TimeDelta kMaxProgressivePaintTime = // The scrollbar will always be responsive since it is managed by a separate // process. constexpr base::TimeDelta kMaxInitialProgressivePaintTime = - base::TimeDelta::FromMilliseconds(250); + base::Milliseconds(250); FontMappingMode g_font_mapping_mode = FontMappingMode::kNoMapping; @@ -945,6 +944,10 @@ bool PDFiumEngine::HandleInputEvent(const blink::WebInputEvent& event) { case blink::WebInputEvent::Type::kMouseEnter: OnMouseEnter(static_cast(event)); break; + case blink::WebInputEvent::Type::kKeyDown: + // Blink mostly sends `kRawKeyDown`, but sometimes generates `kKeyDown`, + // such as when tabbing between frames. Pepper treats them equivalently + // (see content/renderer/pepper/event_conversion.cc), so we will, too. case blink::WebInputEvent::Type::kRawKeyDown: rv = OnKeyDown(static_cast(event)); break; @@ -3743,6 +3746,12 @@ void PDFiumEngine::OnSelectionPositionChanged() { gfx::Size PDFiumEngine::ApplyDocumentLayout( const DocumentLayout::Options& options) { + layout_.SetOptions(options); + + // Don't actually update layout until the document finishes loading. + if (!document_loaded_) + return layout_.size(); + // We need to return early if the layout would not change, otherwise calling // client_->ScrollToPage() would send another "viewport" message, triggering // an infinite loop. @@ -3750,7 +3759,6 @@ gfx::Size PDFiumEngine::ApplyDocumentLayout( // TODO(crbug.com/1013800): The current implementation computes layout twice // (here, and in InvalidateAllPages()). This shouldn't be too expensive at // realistic page counts, but could be avoided. - layout_.SetOptions(options); UpdateDocumentLayout(&layout_); if (!layout_.dirty()) return layout_.size(); @@ -4273,7 +4281,7 @@ void PDFiumEngine::SetLinkUnderCursorForAnnotation(FPDF_ANNOTATION annot, void PDFiumEngine::RequestThumbnail(int page_index, float device_pixel_ratio, SendThumbnailCallback send_callback) { - DCHECK(PageIndexInBounds(page_index)); + CHECK(PageIndexInBounds(page_index)); // Thumbnails cannot be generated in the middle of a progressive paint of a // page. Generate the thumbnail immediately only if the page is not currently diff --git a/chromium/pdf/pdfium/pdfium_engine_unittest.cc b/chromium/pdf/pdfium/pdfium_engine_unittest.cc index 81116ae81ae..67eb5cbcd89 100644 --- a/chromium/pdf/pdfium/pdfium_engine_unittest.cc +++ b/chromium/pdf/pdfium/pdfium_engine_unittest.cc @@ -29,8 +29,10 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/input/web_input_event.h" +#include "third_party/blink/public/common/input/web_keyboard_event.h" #include "third_party/blink/public/common/input/web_mouse_event.h" #include "third_party/blink/public/common/input/web_pointer_properties.h" +#include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point_f.h" #include "ui/gfx/geometry/rect.h" @@ -77,6 +79,8 @@ class MockTestClient : public TestClient { int32_t result, base::TimeDelta delay), (override)); + MOCK_METHOD(void, DocumentFocusChanged, (bool), (override)); + MOCK_METHOD(void, SetLinkUnderCursor, (const std::string&), (override)); }; } // namespace @@ -129,6 +133,17 @@ class PDFiumEngineTest : public PDFiumTestBase { return loaded_incrementally; } + void FinishWithPluginSizeUpdated(MockTestClient& client, + PDFiumEngine& engine) { + ResultCallback callback; + EXPECT_CALL(client, ScheduleTaskOnMainThread) + .WillOnce(MoveArg<1>(&callback)); + engine.PluginSizeUpdated({}); + + ASSERT_TRUE(callback); + std::move(callback).Run(0); + } + // Counts the number of available pages. Returns `int` instead of `size_t` for // consistency with `PDFiumEngine::GetNumberOfPages()`. int CountAvailablePages(const PDFiumEngine& engine) { @@ -249,6 +264,23 @@ TEST_F(PDFiumEngineTest, ProposeDocumentLayoutWithOverlap) { engine->RotateCounterclockwise(); } +TEST_F(PDFiumEngineTest, ApplyDocumentLayoutBeforePluginSizeUpdated) { + NiceMock client; + InitializeEngineResult initialize_result = InitializeEngineWithoutLoading( + &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf")); + ASSERT_TRUE(initialize_result.engine); + initialize_result.FinishLoading(); + PDFiumEngine& engine = *initialize_result.engine; + + DocumentLayout::Options options; + options.RotatePagesClockwise(); + EXPECT_CALL(client, ScrollToPage(-1)).Times(0); + EXPECT_EQ(gfx::Size(343, 1664), engine.ApplyDocumentLayout(options)); + + EXPECT_CALL(client, ScrollToPage(-1)).Times(1); + ASSERT_NO_FATAL_FAILURE(FinishWithPluginSizeUpdated(client, engine)); +} + TEST_F(PDFiumEngineTest, ApplyDocumentLayoutAvoidsInfiniteLoop) { NiceMock client; std::unique_ptr engine = InitializeEngine( @@ -477,14 +509,8 @@ TEST_F(PDFiumEngineTest, PluginSizeUpdatedAfterLoad) { ASSERT_TRUE(initialize_result.engine); PDFiumEngine& engine = *initialize_result.engine; - ResultCallback callback; - EXPECT_CALL(client, ScheduleTaskOnMainThread).WillOnce(MoveArg<1>(&callback)); - initialize_result.FinishLoading(); - engine.PluginSizeUpdated({}); - - ASSERT_TRUE(callback); - std::move(callback).Run(0); + ASSERT_NO_FATAL_FAILURE(FinishWithPluginSizeUpdated(client, engine)); EXPECT_EQ(engine.GetNumberOfPages(), CountAvailablePages(engine)); } @@ -597,6 +623,35 @@ TEST_F(PDFiumEngineTest, RequestThumbnailLinearized) { initialize_result.FinishLoading(); } +TEST_F(PDFiumEngineTest, HandleInputEventKeyDown) { + NiceMock client; + std::unique_ptr engine = + InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf")); + ASSERT_TRUE(engine); + EXPECT_CALL(client, DocumentFocusChanged(true)); + + blink::WebKeyboardEvent key_down_event( + blink::WebInputEvent::Type::kKeyDown, blink::WebInputEvent::kNoModifiers, + blink::WebInputEvent::GetStaticTimeStampForTests()); + key_down_event.windows_key_code = ui::VKEY_TAB; + EXPECT_TRUE(engine->HandleInputEvent(key_down_event)); +} + +TEST_F(PDFiumEngineTest, HandleInputEventRawKeyDown) { + NiceMock client; + std::unique_ptr engine = + InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf")); + ASSERT_TRUE(engine); + EXPECT_CALL(client, DocumentFocusChanged(true)); + + blink::WebKeyboardEvent raw_key_down_event( + blink::WebInputEvent::Type::kRawKeyDown, + blink::WebInputEvent::kNoModifiers, + blink::WebInputEvent::GetStaticTimeStampForTests()); + raw_key_down_event.windows_key_code = ui::VKEY_TAB; + EXPECT_TRUE(engine->HandleInputEvent(raw_key_down_event)); +} + using PDFiumEngineDeathTest = PDFiumEngineTest; TEST_F(PDFiumEngineDeathTest, RequestThumbnailRedundant) { @@ -622,18 +677,6 @@ TEST_F(PDFiumEngineDeathTest, RequestThumbnailRedundant) { /*page_index=*/1, /*device_pixel_ratio=*/1, mock_callback.Get())); } -class TabbingTestClient : public TestClient { - public: - TabbingTestClient() = default; - ~TabbingTestClient() override = default; - TabbingTestClient(const TabbingTestClient&) = delete; - TabbingTestClient& operator=(const TabbingTestClient&) = delete; - - // Mock PDFEngine::Client methods. - MOCK_METHOD(void, DocumentFocusChanged, (bool), (override)); - MOCK_METHOD(void, SetLinkUnderCursor, (const std::string&), (override)); -}; - class PDFiumEngineTabbingTest : public PDFiumTestBase { public: PDFiumEngineTabbingTest() = default; @@ -690,7 +733,7 @@ TEST_F(PDFiumEngineTabbingTest, LinkUnderCursorTest) { scoped_feature_list.InitAndEnableFeature( chrome_pdf::features::kTabAcrossPDFAnnotations); - TabbingTestClient client; + NiceMock client; std::unique_ptr engine = InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf")); ASSERT_TRUE(engine); @@ -786,7 +829,7 @@ TEST_F(PDFiumEngineTabbingTest, TabbingForwardTest) { * ++ Page 2 * ++++ Annotation */ - NiceMock client; + NiceMock client; std::unique_ptr engine = InitializeEngine( &client, FILE_PATH_LITERAL("annotation_form_fields.pdf")); ASSERT_TRUE(engine); @@ -838,7 +881,7 @@ TEST_F(PDFiumEngineTabbingTest, TabbingBackwardTest) { * ++ Page 2 * ++++ Annotation */ - NiceMock client; + NiceMock client; std::unique_ptr engine = InitializeEngine( &client, FILE_PATH_LITERAL("annotation_form_fields.pdf")); ASSERT_TRUE(engine); @@ -945,7 +988,7 @@ TEST_F(PDFiumEngineTabbingTest, NoFocusableItemTabbingTest) { * ++ Page 1 * ++ Page 2 */ - NiceMock client; + NiceMock client; std::unique_ptr engine = InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf")); ASSERT_TRUE(engine); @@ -994,7 +1037,7 @@ TEST_F(PDFiumEngineTabbingTest, RestoringDocumentFocusTest) { * ++ Page 2 * ++++ Annotation */ - NiceMock client; + NiceMock client; std::unique_ptr engine = InitializeEngine( &client, FILE_PATH_LITERAL("annotation_form_fields.pdf")); ASSERT_TRUE(engine); @@ -1039,7 +1082,7 @@ TEST_F(PDFiumEngineTabbingTest, RestoringAnnotFocusTest) { * ++ Page 2 * ++++ Annotation */ - NiceMock client; + NiceMock client; std::unique_ptr engine = InitializeEngine( &client, FILE_PATH_LITERAL("annotation_form_fields.pdf")); ASSERT_TRUE(engine); diff --git a/chromium/pdf/pdfium/pdfium_form_filler.cc b/chromium/pdf/pdfium/pdfium_form_filler.cc index e16f3c65ff3..829962e9bda 100644 --- a/chromium/pdf/pdfium/pdfium_form_filler.cc +++ b/chromium/pdf/pdfium/pdfium_form_filler.cc @@ -5,6 +5,7 @@ #include "pdf/pdfium/pdfium_form_filler.h" #include +#include #include #include @@ -18,8 +19,10 @@ #include "pdf/pdf_features.h" #include "pdf/pdfium/pdfium_engine.h" #include "third_party/blink/public/common/input/web_input_event.h" +#include "third_party/blink/public/web/blink.h" #include "third_party/pdfium/public/fpdf_annot.h" #include "ui/gfx/geometry/rect.h" +#include "v8/include/v8-isolate.h" namespace chrome_pdf { @@ -44,7 +47,7 @@ PDFiumFormFiller::ScriptOption PDFiumFormFiller::DefaultScriptOption() { PDFiumFormFiller::PDFiumFormFiller(PDFiumEngine* engine, ScriptOption script_option) - : engine_(engine), script_option_(script_option) { + : engine_in_isolate_scope_factory_(engine), script_option_(script_option) { // Initialize FPDF_FORMFILLINFO member variables. Deriving from this struct // allows the static callbacks to be able to cast the FPDF_FORMFILLINFO in // callbacks to ourself instead of maintaining a map of them to @@ -132,7 +135,8 @@ void PDFiumFormFiller::Form_Invalidate(FPDF_FORMFILLINFO* param, double top, double right, double bottom) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); int page_index = engine->GetVisiblePageIndex(page); if (page_index == -1) { // This can sometime happen when the page is closed because it went off @@ -153,7 +157,8 @@ void PDFiumFormFiller::Form_OutputSelectedRect(FPDF_FORMFILLINFO* param, double top, double right, double bottom) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); int page_index = engine->GetVisiblePageIndex(page); if (page_index == -1) return; @@ -179,8 +184,7 @@ int PDFiumFormFiller::Form_SetTimer(FPDF_FORMFILLINFO* param, int elapse, TimerCallback timer_func) { auto* form_filler = static_cast(param); - return form_filler->SetTimer(base::TimeDelta::FromMilliseconds(elapse), - timer_func); + return form_filler->SetTimer(base::Milliseconds(elapse), timer_func); } // static @@ -209,7 +213,8 @@ FPDF_SYSTEMTIME PDFiumFormFiller::Form_GetLocalTime(FPDF_FORMFILLINFO* param) { // static void PDFiumFormFiller::Form_OnChange(FPDF_FORMFILLINFO* param) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->EnteredEditMode(); } @@ -217,7 +222,8 @@ void PDFiumFormFiller::Form_OnChange(FPDF_FORMFILLINFO* param) { FPDF_PAGE PDFiumFormFiller::Form_GetPage(FPDF_FORMFILLINFO* param, FPDF_DOCUMENT document, int page_index) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); if (!engine->PageIndexInBounds(page_index)) return nullptr; return engine->pages_[page_index]->GetPage(); @@ -226,7 +232,8 @@ FPDF_PAGE PDFiumFormFiller::Form_GetPage(FPDF_FORMFILLINFO* param, // static FPDF_PAGE PDFiumFormFiller::Form_GetCurrentPage(FPDF_FORMFILLINFO* param, FPDF_DOCUMENT document) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); int index = engine->last_focused_page_; if (index == -1) { index = engine->GetMostVisiblePage(); @@ -247,7 +254,8 @@ int PDFiumFormFiller::Form_GetRotation(FPDF_FORMFILLINFO* param, // static void PDFiumFormFiller::Form_ExecuteNamedAction(FPDF_FORMFILLINFO* param, FPDF_BYTESTRING named_action) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); std::string action(named_action); if (action == "Print") { engine->client_->Print(); @@ -294,7 +302,8 @@ void PDFiumFormFiller::Form_SetTextFieldFocus(FPDF_FORMFILLINFO* param, void PDFiumFormFiller::Form_OnFocusChange(FPDF_FORMFILLINFO* param, FPDF_ANNOTATION annot, int page_index) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); if (!engine->PageIndexInBounds(page_index)) return; @@ -309,7 +318,8 @@ void PDFiumFormFiller::Form_OnFocusChange(FPDF_FORMFILLINFO* param, // static void PDFiumFormFiller::Form_DoURIAction(FPDF_FORMFILLINFO* param, FPDF_BYTESTRING uri) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->client_->NavigateTo(std::string(uri), WindowOpenDisposition::CURRENT_TAB); } @@ -320,7 +330,8 @@ void PDFiumFormFiller::Form_DoGoToAction(FPDF_FORMFILLINFO* param, int zoom_mode, float* position_array, int size_of_array) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->ScrollToPage(page_index); } @@ -329,7 +340,8 @@ void PDFiumFormFiller::Form_DoURIActionWithKeyboardModifier( FPDF_FORMFILLINFO* param, FPDF_BYTESTRING uri, int modifiers) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); bool middle_button = !!(modifiers & blink::WebInputEvent::Modifiers::kMiddleButtonDown); bool alt_key = !!(modifiers & blink::WebInputEvent::Modifiers::kAltKey); @@ -360,7 +372,8 @@ void PDFiumFormFiller::Form_EmailTo(FPDF_FORMFILLINFO* param, std::string bcc_str = WideStringToString(bcc); std::string message_str = WideStringToString(message); - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->client_->Email(to_str, cc_str, bcc_str, subject_str, message_str); } @@ -377,14 +390,16 @@ void PDFiumFormFiller::Form_DisplayCaret(FPDF_FORMFILLINFO* param, void PDFiumFormFiller::Form_SetCurrentPage(FPDF_FORMFILLINFO* param, FPDF_DOCUMENT document, int page) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->ScrollToPage(page); } // static int PDFiumFormFiller::Form_GetCurrentPageIndex(FPDF_FORMFILLINFO* param, FPDF_DOCUMENT document) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); return engine->GetMostVisiblePage(); } @@ -395,7 +410,8 @@ void PDFiumFormFiller::Form_GetPageViewRect(FPDF_FORMFILLINFO* param, double* top, double* right, double* bottom) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); int page_index = engine->GetVisiblePageIndex(page); if (!engine->PageIndexInBounds(page_index)) { *left = 0; @@ -474,7 +490,8 @@ void PDFiumFormFiller::Form_PageEvent(FPDF_FORMFILLINFO* param, DCHECK(event_type == FXFA_PAGEVIEWEVENT_POSTADDED || event_type == FXFA_PAGEVIEWEVENT_POSTREMOVED); - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->UpdatePageCount(); } @@ -579,7 +596,8 @@ int PDFiumFormFiller::Form_Alert(IPDF_JSPLATFORM* param, ALERT_RESULT_YES }; - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); std::string message_str = WideStringToString(message); if (type == ALERT_TYPE_OK) { engine->client_->Alert(message_str); @@ -594,7 +612,8 @@ int PDFiumFormFiller::Form_Alert(IPDF_JSPLATFORM* param, // static void PDFiumFormFiller::Form_Beep(IPDF_JSPLATFORM* param, int type) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->client_->Beep(); } @@ -610,7 +629,8 @@ int PDFiumFormFiller::Form_Response(IPDF_JSPLATFORM* param, std::string question_str = WideStringToString(question); std::string default_str = WideStringToString(default_response); - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); std::string rv = engine->client_->Prompt(question_str, default_str); std::u16string rv_16 = base::UTF8ToUTF16(rv); int rv_bytes = rv_16.size() * sizeof(char16_t); @@ -625,7 +645,8 @@ int PDFiumFormFiller::Form_Response(IPDF_JSPLATFORM* param, int PDFiumFormFiller::Form_GetFilePath(IPDF_JSPLATFORM* param, void* file_path, int length) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); std::string rv = engine->client_->GetURL(); if (file_path && rv.size() <= static_cast(length)) memcpy(file_path, rv.c_str(), rv.size()); @@ -650,7 +671,8 @@ void PDFiumFormFiller::Form_Mail(IPDF_JSPLATFORM* param, std::string subject_str = WideStringToString(subject); std::string message_str = WideStringToString(message); - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->client_->Email(to_str, cc_str, bcc_str, subject_str, message_str); } @@ -666,7 +688,8 @@ void PDFiumFormFiller::Form_Print(IPDF_JSPLATFORM* param, FPDF_BOOL annotations) { // No way to pass the extra information to the print dialog using JavaScript. // Just opening it is fine for now. - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->client_->Print(); } @@ -676,28 +699,67 @@ void PDFiumFormFiller::Form_SubmitForm(IPDF_JSPLATFORM* param, int length, FPDF_WIDESTRING url) { std::string url_str = WideStringToString(url); - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->client_->SubmitForm(url_str, form_data, length); } // static void PDFiumFormFiller::Form_GotoPage(IPDF_JSPLATFORM* param, int page_number) { - PDFiumEngine* engine = GetEngine(param); + EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param); + PDFiumEngine* engine = engine_scope.engine(); engine->ScrollToPage(page_number); } #endif // defined(PDF_ENABLE_V8) +PDFiumFormFiller::EngineInIsolateScope::EngineInIsolateScope( + PDFiumEngine* engine, + v8::Isolate* isolate) + : isolate_scope_(isolate ? std::make_unique(isolate) + : nullptr), + engine_(engine) { + DCHECK(engine_); +} + +PDFiumFormFiller::EngineInIsolateScope::EngineInIsolateScope( + EngineInIsolateScope&&) = default; + +PDFiumFormFiller::EngineInIsolateScope& +PDFiumFormFiller::EngineInIsolateScope::operator=(EngineInIsolateScope&&) = + default; + +PDFiumFormFiller::EngineInIsolateScope::~EngineInIsolateScope() = default; + +PDFiumFormFiller::EngineInIsolateScopeFactory::EngineInIsolateScopeFactory( + PDFiumEngine* engine) + : engine_(engine), callback_isolate_(v8::Isolate::TryGetCurrent()) { + if (callback_isolate_) + CHECK_EQ(blink::MainThreadIsolate(), callback_isolate_); +} + +PDFiumFormFiller::EngineInIsolateScopeFactory::~EngineInIsolateScopeFactory() = + default; + +PDFiumFormFiller::EngineInIsolateScope +PDFiumFormFiller::EngineInIsolateScopeFactory::GetEngineInIsolateScope() const { + return EngineInIsolateScope(engine_, callback_isolate_); +} + // static -PDFiumEngine* PDFiumFormFiller::GetEngine(FPDF_FORMFILLINFO* info) { +PDFiumFormFiller::EngineInIsolateScope +PDFiumFormFiller::GetEngineInIsolateScope(FPDF_FORMFILLINFO* info) { auto* form_filler = static_cast(info); - return form_filler->engine_; + return form_filler->engine_in_isolate_scope_factory_ + .GetEngineInIsolateScope(); } // static -PDFiumEngine* PDFiumFormFiller::GetEngine(IPDF_JSPLATFORM* platform) { +PDFiumFormFiller::EngineInIsolateScope +PDFiumFormFiller::GetEngineInIsolateScope(IPDF_JSPLATFORM* platform) { auto* form_filler = static_cast(platform); - return form_filler->engine_; + return form_filler->engine_in_isolate_scope_factory_ + .GetEngineInIsolateScope(); } int PDFiumFormFiller::SetTimer(const base::TimeDelta& delay, diff --git a/chromium/pdf/pdfium/pdfium_form_filler.h b/chromium/pdf/pdfium/pdfium_form_filler.h index 53b206e7d66..aabd121a747 100644 --- a/chromium/pdf/pdfium/pdfium_form_filler.h +++ b/chromium/pdf/pdfium/pdfium_form_filler.h @@ -12,6 +12,7 @@ #include "base/timer/timer.h" #include "third_party/pdfium/public/fpdf_formfill.h" #include "third_party/pdfium/public/fpdfview.h" +#include "v8/include/v8-isolate.h" namespace chrome_pdf { @@ -189,13 +190,53 @@ class PDFiumFormFiller : public FPDF_FORMFILLINFO, public IPDF_JSPLATFORM { static void Form_GotoPage(IPDF_JSPLATFORM* param, int page_number); #endif // defined(PDF_ENABLE_V8) - static PDFiumEngine* GetEngine(FPDF_FORMFILLINFO* info); - static PDFiumEngine* GetEngine(IPDF_JSPLATFORM* platform); + // A utility class that helps in enforcing accesses of `PDFiumEngine` within a + // given `v8::Isolate`. The entries of the isolates are scoped to the + // lifetimes of its instances. This class is tolerant of null isolates. + class EngineInIsolateScope { + public: + EngineInIsolateScope(PDFiumEngine* engine, v8::Isolate* isolate); + EngineInIsolateScope(EngineInIsolateScope&&); + EngineInIsolateScope& operator=(EngineInIsolateScope&&); + ~EngineInIsolateScope(); + + PDFiumEngine* engine() { return engine_; } + + private: + std::unique_ptr isolate_scope_; + PDFiumEngine* engine_; + }; + + class EngineInIsolateScopeFactory { + public: + explicit EngineInIsolateScopeFactory(PDFiumEngine* engine); + EngineInIsolateScopeFactory(const EngineInIsolateScope&) = delete; + EngineInIsolateScopeFactory& operator=( + const EngineInIsolateScopeFactory&&) = delete; + ~EngineInIsolateScopeFactory(); + + // Retrieves `engine_` while attempting to enter `callback_isolate_`. + EngineInIsolateScope GetEngineInIsolateScope() const; + + private: + PDFiumEngine* const engine_; + + // The V8 isolate to enter inside callbacks from PDFium. Can be `nullptr` + // because indirect callers of `PDFiumFormFiller` might not be embedding V8 + // separately. This can happen in utility processes (through callers of + // //pdf/pdf.h) and in Pepper plugin processes. + v8::Isolate* const callback_isolate_; + }; + + // Gets an `EngineInIsolateScope` using `engine_in_isolate_scope_factory_`. + static EngineInIsolateScope GetEngineInIsolateScope(FPDF_FORMFILLINFO* info); + static EngineInIsolateScope GetEngineInIsolateScope( + IPDF_JSPLATFORM* platform); int SetTimer(const base::TimeDelta& delay, TimerCallback timer_func); void KillTimer(int timer_id); - PDFiumEngine* const engine_; + const EngineInIsolateScopeFactory engine_in_isolate_scope_factory_; const ScriptOption script_option_; std::map> timers_; }; diff --git a/chromium/pdf/pdfium/pdfium_form_filler_unittest.cc b/chromium/pdf/pdfium/pdfium_form_filler_unittest.cc index c86242b57b9..24b6e9dc48e 100644 --- a/chromium/pdf/pdfium/pdfium_form_filler_unittest.cc +++ b/chromium/pdf/pdfium/pdfium_form_filler_unittest.cc @@ -2,22 +2,29 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "pdf/pdfium/pdfium_form_filler.h" + +#include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" +#include "gin/public/isolate_holder.h" #include "pdf/pdfium/pdfium_engine.h" #include "pdf/pdfium/pdfium_test_base.h" #include "pdf/test/test_client.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/common/input/web_input_event.h" +#include "third_party/blink/public/web/blink.h" #include "third_party/pdfium/public/fpdf_annot.h" +#include "third_party/pdfium/public/fpdf_formfill.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/size.h" - -using testing::InSequence; +#include "v8/include/v8-isolate.h" namespace chrome_pdf { namespace { +using ::testing::InSequence; + class FormFillerTestClient : public TestClient { public: FormFillerTestClient() = default; @@ -26,6 +33,7 @@ class FormFillerTestClient : public TestClient { FormFillerTestClient& operator=(const FormFillerTestClient&) = delete; // Mock PDFEngine::Client methods. + MOCK_METHOD(void, Beep, (), (override)); MOCK_METHOD(void, ScrollToX, (int), (override)); MOCK_METHOD(void, ScrollToY, (int), (override)); MOCK_METHOD(void, @@ -58,6 +66,12 @@ class FormFillerTest : public PDFiumTestBase { engine->form_filler_.Form_DoURIActionWithKeyboardModifier( &engine->form_filler_, uri, modifiers); } + + void TriggerBeep(PDFiumEngine* engine) { + ASSERT_TRUE(engine); + engine->form_filler_.Form_Beep(&engine->form_filler_, + JSPLATFORM_BEEP_DEFAULT); + } }; TEST_F(FormFillerTest, DoURIActionWithKeyboardModifier) { @@ -161,4 +175,40 @@ TEST_F(FormFillerTest, FormOnFocusChange) { } } +class FormFillerIsolateTest : public FormFillerTest { + public: + FormFillerIsolateTest() { + // Needed for setting up V8. + InitializeSDK(/*enable_v8=*/true, FontMappingMode::kNoMapping); + } + + ~FormFillerIsolateTest() override { ShutdownSDK(); } +}; + +TEST_F(FormFillerIsolateTest, IsolateScoping) { + // Enter the embedder's isolate so it can be captured when the + // `PDFiumFormFiller` is created. + v8::Isolate* embedder_isolate = blink::MainThreadIsolate(); + v8::Isolate::Scope embedder_isolate_scope(embedder_isolate); + + FormFillerTestClient client; + PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript); + + gin::IsolateHolder pdfium_test_isolate_holder( + base::ThreadTaskRunnerHandle::Get(), + gin::IsolateHolder::IsolateType::kTest); + v8::Isolate* pdfium_test_isolate = pdfium_test_isolate_holder.isolate(); + + // Enter PDFium's isolate and then trigger a beep callback. The embedder's + // isolate should be entered during the callback's execution. + v8::Isolate::Scope pdfium_test_isolate_scope(pdfium_test_isolate); + EXPECT_CALL(client, Beep).WillOnce([&embedder_isolate]() { + EXPECT_EQ(v8::Isolate::TryGetCurrent(), embedder_isolate); + }); + TriggerBeep(&engine); + + // PDFium's isolate should be entered again after the callback completes. + EXPECT_EQ(v8::Isolate::TryGetCurrent(), pdfium_test_isolate); +} + } // namespace chrome_pdf diff --git a/chromium/pdf/pdfium/pdfium_print_unittest.cc b/chromium/pdf/pdfium/pdfium_print_unittest.cc index 128f109e812..9dd62a9bdb7 100644 --- a/chromium/pdf/pdfium/pdfium_print_unittest.cc +++ b/chromium/pdf/pdfium/pdfium_print_unittest.cc @@ -23,7 +23,7 @@ #include "third_party/skia/include/core/SkImageInfo.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size_f.h" -#include "ui/gfx/skia_util.h" +#include "ui/gfx/geometry/skia_conversions.h" namespace chrome_pdf { diff --git a/chromium/pdf/ppapi_migration/graphics.cc b/chromium/pdf/ppapi_migration/graphics.cc index 0083a0ef521..4b03fb30723 100644 --- a/chromium/pdf/ppapi_migration/graphics.cc +++ b/chromium/pdf/ppapi_migration/graphics.cc @@ -26,8 +26,8 @@ #include "ui/gfx/blit.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/skia_conversions.h" #include "ui/gfx/geometry/vector2d.h" -#include "ui/gfx/skia_util.h" namespace chrome_pdf { diff --git a/chromium/pdf/ppapi_migration/graphics_unittest.cc b/chromium/pdf/ppapi_migration/graphics_unittest.cc index 2269534504b..64eb2157e37 100644 --- a/chromium/pdf/ppapi_migration/graphics_unittest.cc +++ b/chromium/pdf/ppapi_migration/graphics_unittest.cc @@ -22,7 +22,7 @@ #include "third_party/skia/include/core/SkSurface.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/skia_util.h" +#include "ui/gfx/geometry/skia_conversions.h" namespace chrome_pdf { diff --git a/chromium/pdf/ppapi_migration/input_event_conversions.cc b/chromium/pdf/ppapi_migration/input_event_conversions.cc index 8ff8228c27e..16cc97a3fab 100644 --- a/chromium/pdf/ppapi_migration/input_event_conversions.cc +++ b/chromium/pdf/ppapi_migration/input_event_conversions.cc @@ -43,15 +43,9 @@ blink::WebInputEvent::Type GetWebInputEventType(PP_InputEvent_Type event_type) { case PP_INPUTEVENT_TYPE_WHEEL: return blink::WebInputEvent::Type::kMouseWheel; case PP_INPUTEVENT_TYPE_RAWKEYDOWN: - case PP_INPUTEVENT_TYPE_KEYDOWN: - // Blink no longer passes `kKeyDown` events into plugins, and instead - // passes `kRawKeyDown` events. However, `kRawKeyDown` gets mapped to - // `PP_INPUTEVENT_TYPE_KEYDOWN` for backwards compatibility. Map both - // Pepper enums to `kRawKeyDown` to allow for a common implementation - // between the Pepper and Pepper-free plugins. - // See the comments inside the definition of `ConvertEventTypes()` in - // content/renderer/pepper/event_conversion.cc. return blink::WebInputEvent::Type::kRawKeyDown; + case PP_INPUTEVENT_TYPE_KEYDOWN: + return blink::WebInputEvent::Type::kKeyDown; case PP_INPUTEVENT_TYPE_KEYUP: return blink::WebInputEvent::Type::kKeyUp; case PP_INPUTEVENT_TYPE_CHAR: @@ -95,7 +89,7 @@ std::unique_ptr GetWebMouseEvent( auto mouse_event = std::make_unique( type, event.GetModifiers(), - base::TimeTicks() + base::TimeDelta::FromSecondsD(event.GetTimeStamp())); + base::TimeTicks() + base::Seconds(event.GetTimeStamp())); mouse_event->button = GetWebPointerPropertiesButton(event.GetButton()); mouse_event->click_count = event.GetClickCount(); @@ -113,7 +107,7 @@ std::unique_ptr GetWebKeyboardEvent( auto keyboard_event = std::make_unique( type, event.GetModifiers(), - base::TimeTicks() + base::TimeDelta::FromSecondsD(event.GetTimeStamp())); + base::TimeTicks() + base::Seconds(event.GetTimeStamp())); keyboard_event->windows_key_code = event.GetKeyCode(); @@ -136,7 +130,7 @@ std::unique_ptr GetWebTouchEvent( auto touch_event = std::make_unique( type, event.GetModifiers(), - base::TimeTicks() + base::TimeDelta::FromSecondsD(event.GetTimeStamp())); + base::TimeTicks() + base::Seconds(event.GetTimeStamp())); // The PDF plugin only cares about the first touch and the number of touches, // but copy over all the touches so that `touch_event->touches_length` @@ -164,6 +158,7 @@ std::unique_ptr GetWebInputEvent( case blink::WebInputEvent::Type::kMouseLeave: return GetWebMouseEvent(pp::MouseInputEvent(event)); case blink::WebInputEvent::Type::kRawKeyDown: + case blink::WebInputEvent::Type::kKeyDown: case blink::WebInputEvent::Type::kKeyUp: case blink::WebInputEvent::Type::kChar: return GetWebKeyboardEvent(pp::KeyboardInputEvent(event)); diff --git a/chromium/pdf/ppapi_migration/url_loader.cc b/chromium/pdf/ppapi_migration/url_loader.cc index 6d2d89ec228..59eadb3b795 100644 --- a/chromium/pdf/ppapi_migration/url_loader.cc +++ b/chromium/pdf/ppapi_migration/url_loader.cc @@ -345,7 +345,6 @@ void PepperUrlLoader::Open(const UrlRequest& request, ResultCallback callback) { pp::URLRequestInfo pp_request(plugin_instance_); pp_request.SetURL(request.url); pp_request.SetMethod(request.method); - pp_request.SetCustomReferrerURL(request.url); if (request.ignore_redirects) pp_request.SetFollowRedirects(false); diff --git a/chromium/pdf/url_loader_wrapper_impl.cc b/chromium/pdf/url_loader_wrapper_impl.cc index 5eb00bc08de..add261cd6fb 100644 --- a/chromium/pdf/url_loader_wrapper_impl.cc +++ b/chromium/pdf/url_loader_wrapper_impl.cc @@ -29,7 +29,7 @@ namespace chrome_pdf { namespace { // We should read with delay to prevent block UI thread, and reduce CPU usage. -constexpr base::TimeDelta kReadDelayMs = base::TimeDelta::FromMilliseconds(2); +constexpr base::TimeDelta kReadDelayMs = base::Milliseconds(2); UrlRequest MakeRangeRequest(const std::string& url, const std::string& referrer_url, -- cgit v1.2.1