// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "pdf/pdf_view_web_plugin.h" #include #include #include #include #include #include #include "base/containers/span.h" #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/run_loop.h" #include "base/strings/string_piece.h" #include "base/test/bind.h" #include "base/test/values_test_util.h" #include "base/time/time.h" #include "base/values.h" #include "cc/paint/paint_canvas.h" #include "cc/test/pixel_comparator.h" #include "cc/test/pixel_test_utils.h" #include "mojo/public/cpp/bindings/associated_receiver.h" #include "mojo/public/cpp/bindings/associated_remote.h" #include "net/cookies/site_for_cookies.h" #include "pdf/accessibility_structs.h" #include "pdf/buildflags.h" #include "pdf/content_restriction.h" #include "pdf/document_layout.h" #include "pdf/mojom/pdf.mojom.h" #include "pdf/paint_ready_rect.h" #include "pdf/pdf_accessibility_data_handler.h" #include "pdf/pdf_view_plugin_base.h" #include "pdf/test/mock_web_associated_url_loader.h" #include "pdf/test/test_helpers.h" #include "pdf/test/test_pdfium_engine.h" #include "printing/metafile_skia.h" #include "services/network/public/mojom/referrer_policy.mojom-shared.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/loader/http_body_element_type.h" #include "third_party/blink/public/platform/web_data.h" #include "third_party/blink/public/platform/web_http_body.h" #include "third_party/blink/public/platform/web_http_header_visitor.h" #include "third_party/blink/public/platform/web_input_event_result.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" #include "third_party/blink/public/platform/web_url_request.h" #include "third_party/blink/public/platform/web_url_response.h" #include "third_party/blink/public/web/web_associated_url_loader.h" #include "third_party/blink/public/web/web_associated_url_loader_client.h" #include "third_party/blink/public/web/web_plugin_container.h" #include "third_party/blink/public/web/web_plugin_params.h" #include "third_party/blink/public/web/web_print_params.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkSurface.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.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_f.h" #include "ui/gfx/range/range.h" #include "ui/latency/latency_info.h" #include "url/gurl.h" namespace chrome_pdf { namespace { using ::testing::AnyNumber; using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::InSequence; using ::testing::Invoke; using ::testing::IsEmpty; using ::testing::IsFalse; using ::testing::IsTrue; using ::testing::MockFunction; using ::testing::NiceMock; using ::testing::Pointwise; using ::testing::Return; using ::testing::SaveArg; using ::testing::SizeIs; // `kCanvasSize` needs to be big enough to hold plugin's snapshots during // testing. constexpr gfx::Size kCanvasSize(100, 100); // Note: Make sure `kDefaultColor` is different from `kPaintColor` and the // plugin's background color. This will help identify bitmap changes after // painting. constexpr SkColor kDefaultColor = SK_ColorGREEN; constexpr SkColor kPaintColor = SK_ColorRED; struct PaintParams { // The plugin container's device scale. float device_scale; // The window area in CSS pixels. gfx::Rect window_rect; // 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, "") { PDFEngine::Client::SearchStringResult l = std::get<0>(arg); PDFEngine::Client::SearchStringResult r = std::get<1>(arg); 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; } base::Value::Dict ParseMessage(base::StringPiece json) { return std::move(base::test::ParseJson(json).GetDict()); } // Generates the expected `SkBitmap` with `paint_color` filled in the expected // clipped area and `kDefaultColor` as the background color. SkBitmap GenerateExpectedBitmapForPaint(const gfx::Rect& expected_clipped_rect, SkColor paint_color) { sk_sp expected_surface = CreateSkiaSurfaceForTesting(kCanvasSize, kDefaultColor); expected_surface->getCanvas()->clipIRect( gfx::RectToSkIRect(expected_clipped_rect)); expected_surface->getCanvas()->clear(paint_color); SkBitmap expected_bitmap; expected_surface->makeImageSnapshot()->asLegacyBitmap(&expected_bitmap); return expected_bitmap; } class MockHeaderVisitor : public blink::WebHTTPHeaderVisitor { public: MOCK_METHOD(void, VisitHeader, (const blink::WebString&, const blink::WebString&), (override)); }; class MockPdfAccessibilityDataHandler : public PdfAccessibilityDataHandler { public: // PdfAccessibilityDataHandler: MOCK_METHOD(void, SetAccessibilityViewportInfo, (AccessibilityViewportInfo), (override)); MOCK_METHOD(void, SetAccessibilityDocInfo, (AccessibilityDocInfo), (override)); MOCK_METHOD(void, SetAccessibilityPageInfo, (AccessibilityPageInfo, std::vector, std::vector, AccessibilityPageObjects), (override)); }; class FakePdfViewWebPluginClient : public PdfViewWebPlugin::Client { public: FakePdfViewWebPluginClient() { ON_CALL(*this, CreateAssociatedURLLoader).WillByDefault([]() { auto associated_loader = std::make_unique>(); ON_CALL(*associated_loader, LoadAsynchronously) .WillByDefault([](const blink::WebURLRequest& /*request*/, blink::WebAssociatedURLLoaderClient* client) { // TODO(crbug.com/1322928): Must trigger callback to free // `UrlLoader`. client->DidReceiveResponse(blink::WebURLResponse()); client->DidFinishLoading(); }); return associated_loader; }); ON_CALL(*this, GetEmbedderOriginString) .WillByDefault( Return("chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/")); ON_CALL(*this, HasFrame).WillByDefault(Return(true)); } // PdfViewWebPlugin::Client: MOCK_METHOD(std::unique_ptr, FromV8Value, (v8::Local, v8::Local), (override)); MOCK_METHOD(base::WeakPtr, GetWeakPtr, (), (override)); MOCK_METHOD(std::unique_ptr, CreateEngine, (PDFEngine::Client*, PDFiumFormFiller::ScriptOption), (override)); MOCK_METHOD(void, SetPluginContainer, (blink::WebPluginContainer*), (override)); MOCK_METHOD(blink::WebPluginContainer*, PluginContainer, (), (override)); MOCK_METHOD(net::SiteForCookies, SiteForCookies, (), (const override)); MOCK_METHOD(blink::WebURL, CompleteURL, (const blink::WebString&), (const override)); MOCK_METHOD(void, PostMessage, (base::Value::Dict), (override)); MOCK_METHOD(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, bool), (override)); MOCK_METHOD(void, ReportFindInPageTickmarks, (const std::vector&), (override)); MOCK_METHOD(float, DeviceScaleFactor, (), (override)); MOCK_METHOD(gfx::PointF, GetScrollPosition, (), (override)); MOCK_METHOD(void, UsePluginAsFindHandler, (), (override)); MOCK_METHOD(void, SetReferrerForRequest, (blink::WebURLRequest&, const blink::WebURL&), (override)); MOCK_METHOD(void, Alert, (const blink::WebString&), (override)); MOCK_METHOD(bool, Confirm, (const blink::WebString&), (override)); MOCK_METHOD(blink::WebString, Prompt, (const blink::WebString&, const blink::WebString&), (override)); MOCK_METHOD(void, TextSelectionChanged, (const blink::WebString&, uint32_t, const gfx::Range&), (override)); MOCK_METHOD(std::unique_ptr, CreateAssociatedURLLoader, (const blink::WebAssociatedURLLoaderOptions&), (override)); MOCK_METHOD(void, UpdateTextInputState, (), (override)); MOCK_METHOD(void, UpdateSelectionBounds, (), (override)); MOCK_METHOD(std::string, GetEmbedderOriginString, (), (override)); MOCK_METHOD(bool, HasFrame, (), (const override)); MOCK_METHOD(void, DidStartLoading, (), (override)); MOCK_METHOD(void, DidStopLoading, (), (override)); MOCK_METHOD(void, RecordComputedAction, (const std::string&), (override)); MOCK_METHOD(std::unique_ptr, CreateAccessibilityDataHandler, (PdfAccessibilityActionHandler*), (override)); }; class FakePdfService : public pdf::mojom::PdfService { public: MOCK_METHOD(void, SetListener, (mojo::PendingRemote), (override)); MOCK_METHOD(void, UpdateContentRestrictions, (int32_t), (override)); MOCK_METHOD(void, HasUnsupportedFeature, (), (override)); MOCK_METHOD(void, SaveUrlAs, (const GURL&, network::mojom::ReferrerPolicy), (override)); MOCK_METHOD(void, SelectionChanged, (const gfx::PointF&, int32_t, const gfx::PointF&, int32_t), (override)); MOCK_METHOD(void, SetPluginCanSave, (bool), (override)); }; } // namespace class PdfViewWebPluginWithoutInitializeTest : public testing::Test { protected: // Custom deleter for `plugin_`. PdfViewWebPlugin must be destroyed by // PdfViewWebPlugin::Destroy() instead of its destructor. struct PluginDeleter { void operator()(PdfViewWebPlugin* ptr) { ptr->Destroy(); } }; static void AddToPluginParams(base::StringPiece name, base::StringPiece value, blink::WebPluginParams& params) { params.attribute_names.push_back( blink::WebString::FromUTF8(name.data(), name.size())); params.attribute_values.push_back( blink::WebString::FromUTF8(value.data(), value.size())); } void SetUpPlugin(base::StringPiece document_url, const blink::WebPluginParams& params) { auto client = std::make_unique>(); client_ptr_ = client.get(); ON_CALL(*client_ptr_, CompleteURL) .WillByDefault([parsed_document_url = GURL(document_url)]( const blink::WebString& partial_url) { return parsed_document_url.Resolve(partial_url.Utf8()); }); ON_CALL(*client_ptr_, CreateEngine) .WillByDefault([this]( PDFEngine::Client* client, PDFiumFormFiller::ScriptOption /*script_option*/) { auto engine = std::make_unique>(client); engine_ptr_ = engine.get(); return engine; }); ON_CALL(*client_ptr_, CreateAccessibilityDataHandler) .WillByDefault([this]() { auto handler = std::make_unique>(); accessibility_data_handler_ptr_ = handler.get(); return handler; }); SetUpClient(); plugin_ = std::unique_ptr(new PdfViewWebPlugin( std::move(client), mojo::AssociatedRemote( pdf_receiver_.BindNewEndpointAndPassDedicatedRemote()), params)); } void SetUpPluginWithUrl(const std::string& url) { blink::WebPluginParams params; AddToPluginParams("src", url, params); SetUpPluginParams(params); SetUpPlugin(url, params); } // Allows derived classes to customize plugin parameters within // `SetUpPluginWithUrl()`. virtual void SetUpPluginParams(blink::WebPluginParams& params) {} // Allows derived classes to customize `client_ptr_` within `SetUpPlugin()`. virtual void SetUpClient() {} void TearDown() override { plugin_.reset(); } void ExpectUpdateTextInputState( blink::WebTextInputType expected_text_input_type) { EXPECT_CALL(*client_ptr_, UpdateTextInputState) .WillOnce([this, expected_text_input_type]() { EXPECT_EQ(expected_text_input_type, plugin_->GetPluginTextInputType()); }); } NiceMock pdf_service_; mojo::AssociatedReceiver pdf_receiver_{&pdf_service_}; raw_ptr client_ptr_; std::unique_ptr plugin_; raw_ptr engine_ptr_; raw_ptr accessibility_data_handler_ptr_; }; class PdfViewWebPluginTest : public PdfViewWebPluginWithoutInitializeTest { protected: static constexpr char kPdfUrl[] = "http://localhost/example.pdf"; void SetUp() override { SetUpPluginWithUrl(kPdfUrl); EXPECT_TRUE(plugin_->InitializeForTesting()); } void SetDocumentDimensions(const gfx::Size& dimensions) { EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout) .WillRepeatedly(Return(dimensions)); SendViewportMessage(/*zoom=*/1.0); } void SendViewportMessage(double zoom) { base::Value::Dict message = ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 2, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": 0, "yOffset": 0, "pinchPhase": 0, })"); message.Set("zoom", zoom); plugin_->OnMessage(message); } void UpdatePluginGeometry(float device_scale, const gfx::Rect& window_rect) { UpdatePluginGeometryWithoutWaiting(device_scale, window_rect); // Waits for main thread callback scheduled by `PaintManager`. base::RunLoop run_loop; base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop.QuitClosure()); run_loop.Run(); } void UpdatePluginGeometryWithoutWaiting(float device_scale, const gfx::Rect& window_rect) { // The plugin container's device scale must be set before calling // UpdateGeometry(). EXPECT_CALL(*client_ptr_, DeviceScaleFactor) .WillRepeatedly(Return(device_scale)); plugin_->UpdateGeometry(window_rect, window_rect, window_rect, /*is_visible=*/true); } void TestUpdateGeometrySetsPluginRect(float device_scale, const gfx::Rect& window_rect, float expected_device_scale, const gfx::Rect& expected_plugin_rect) { UpdatePluginGeometryWithoutWaiting(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()) << "Plugin rect comparison failure at device scale of " << device_scale << ", window rect of " << window_rect.ToString(); } void TestPaintEmptySnapshots(float device_scale, const gfx::Rect& window_rect, const gfx::Rect& paint_rect, const gfx::Rect& expected_clipped_rect) { UpdatePluginGeometryWithoutWaiting(device_scale, window_rect); canvas_.DrawColor(kDefaultColor); plugin_->Paint(canvas_.sk_canvas(), paint_rect); // Expect the clipped area on canvas to be filled with plugin's background // color. SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint( expected_clipped_rect, plugin_->GetBackgroundColor()); EXPECT_TRUE( cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap, cc::ExactPixelComparator(/*discard_alpha=*/false))) << "Failure at device scale of " << device_scale << ", window rect of " << window_rect.ToString(); } void TestPaintSnapshots(float device_scale, const gfx::Rect& window_rect, const gfx::Rect& paint_rect, const gfx::Rect& expected_clipped_rect) { UpdatePluginGeometry(device_scale, window_rect); canvas_.DrawColor(kDefaultColor); // Paint the plugin with `kPaintColor`. plugin_->UpdateSnapshot(CreateSkiaImageForTesting( plugin_->GetPluginRectForTesting().size(), kPaintColor)); plugin_->Paint(canvas_.sk_canvas(), paint_rect); // Expect the clipped area on canvas to be filled with `kPaintColor`. SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint(expected_clipped_rect, kPaintColor); EXPECT_TRUE( cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap, cc::ExactPixelComparator(/*discard_alpha=*/false))) << "Failure at device scale of " << device_scale << ", window rect of " << window_rect.ToString(); } // Provides the cc::PaintCanvas for painting. gfx::Canvas canvas_{kCanvasSize, /*image_scale=*/1.0f, /*is_opaque=*/true}; }; class PdfViewWebPluginFullFrameTest : public PdfViewWebPluginTest { protected: void SetUpPluginParams(blink::WebPluginParams& params) override { AddToPluginParams("full-frame", "full-frame", params); } }; TEST_F(PdfViewWebPluginWithoutInitializeTest, Initialize) { SetUpPluginWithUrl("http://localhost/example.pdf"); EXPECT_CALL(*client_ptr_, CreateAssociatedURLLoader) .WillOnce([](const blink::WebAssociatedURLLoaderOptions& options) { EXPECT_TRUE(options.grant_universal_access); auto associated_loader = std::make_unique>(); EXPECT_CALL(*associated_loader, LoadAsynchronously) .WillOnce([](const blink::WebURLRequest& request, blink::WebAssociatedURLLoaderClient* client) { EXPECT_EQ("http://localhost/example.pdf", request.Url().GetString().Utf8()); EXPECT_EQ("GET", request.HttpMethod().Utf8()); EXPECT_TRUE(request.HttpBody().IsNull()); NiceMock header_visitor; EXPECT_CALL(header_visitor, VisitHeader).Times(0); request.VisitHttpHeaderFields(&header_visitor); EXPECT_FALSE(client->WillFollowRedirect(blink::WebURL(), blink::WebURLResponse())); client->DidReceiveResponse(blink::WebURLResponse()); client->DidFinishLoading(); }); return associated_loader; }); EXPECT_CALL(*client_ptr_, SetReferrerForRequest).Times(0); EXPECT_TRUE(plugin_->InitializeForTesting()); } TEST_F(PdfViewWebPluginWithoutInitializeTest, InitializeWithEmptyUrl) { SetUpPluginWithUrl(""); EXPECT_CALL(*client_ptr_, CreateAssociatedURLLoader).Times(0); EXPECT_FALSE(plugin_->InitializeForTesting()); } TEST_F(PdfViewWebPluginWithoutInitializeTest, InitializeForPrintPreview) { SetUpPluginWithUrl("about:blank"); EXPECT_CALL(*client_ptr_, GetEmbedderOriginString) .WillRepeatedly(Return("chrome://print/")); EXPECT_CALL(*client_ptr_, CreateAssociatedURLLoader).Times(0); EXPECT_TRUE(plugin_->InitializeForTesting()); } TEST_F(PdfViewWebPluginTest, CreateUrlLoader) { EXPECT_CALL(*client_ptr_, DidStartLoading).Times(0); EXPECT_CALL(pdf_service_, UpdateContentRestrictions).Times(0); plugin_->CreateUrlLoader(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, plugin_->document_load_state_for_testing()); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginFullFrameTest, CreateUrlLoader) { EXPECT_CALL(*client_ptr_, DidStartLoading); EXPECT_CALL(pdf_service_, UpdateContentRestrictions(kContentRestrictionSave | kContentRestrictionPrint)); plugin_->CreateUrlLoader(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading, plugin_->document_load_state_for_testing()); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginFullFrameTest, CreateUrlLoaderMultipleTimes) { plugin_->CreateUrlLoader(); EXPECT_CALL(*client_ptr_, DidStartLoading).Times(0); plugin_->CreateUrlLoader(); } TEST_F(PdfViewWebPluginFullFrameTest, CreateUrlLoaderAfterDocumentLoadFailed) { plugin_->CreateUrlLoader(); plugin_->DocumentLoadFailed(); EXPECT_CALL(*client_ptr_, DidStartLoading); plugin_->CreateUrlLoader(); } TEST_F(PdfViewWebPluginTest, DocumentLoadComplete) { plugin_->CreateUrlLoader(); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF.LoadSuccess")); EXPECT_CALL(*client_ptr_, PostMessage); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "formFocusChange", "focused": false, })"))); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "printPreviewLoaded", })"))) .Times(0); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); EXPECT_CALL(*client_ptr_, DidStopLoading).Times(0); EXPECT_CALL(pdf_service_, UpdateContentRestrictions).Times(0); plugin_->DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, plugin_->document_load_state_for_testing()); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginFullFrameTest, DocumentLoadComplete) { // Must flush IPCs after `CreateUrlLoader()` in full-frame mode, otherwise // there's an unexpected `UpdateContentRestrictions()` call (see the // `PdfViewWebPluginFullFrameTest.CreateUrlLoader` test). plugin_->CreateUrlLoader(); pdf_receiver_.FlushForTesting(); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF.LoadSuccess")); EXPECT_CALL(*client_ptr_, PostMessage); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "formFocusChange", "focused": false, })"))); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "printPreviewLoaded", })"))) .Times(0); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); EXPECT_CALL(*client_ptr_, DidStopLoading); EXPECT_CALL(pdf_service_, UpdateContentRestrictions(kContentRestrictionPrint | kContentRestrictionPaste | kContentRestrictionCut | kContentRestrictionCopy)); plugin_->DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, plugin_->document_load_state_for_testing()); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, DocumentLoadFailed) { plugin_->CreateUrlLoader(); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF.LoadFailure")); EXPECT_CALL(*client_ptr_, DidStopLoading).Times(0); plugin_->DocumentLoadFailed(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kFailed, plugin_->document_load_state_for_testing()); } TEST_F(PdfViewWebPluginFullFrameTest, DocumentLoadFailed) { plugin_->CreateUrlLoader(); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF.LoadFailure")); EXPECT_CALL(*client_ptr_, DidStopLoading); plugin_->DocumentLoadFailed(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kFailed, plugin_->document_load_state_for_testing()); } TEST_F(PdfViewWebPluginTest, DocumentHasUnsupportedFeature) { EXPECT_CALL(*client_ptr_, RecordComputedAction).Times(AnyNumber()); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF_Unsupported_feature1")); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF_Unsupported_feature2")); // `HasUnsupportedFeature()` is not called if the viewer is not full-frame. EXPECT_CALL(pdf_service_, HasUnsupportedFeature).Times(0); plugin_->DocumentHasUnsupportedFeature("feature1"); plugin_->DocumentHasUnsupportedFeature("feature2"); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, DocumentHasUnsupportedFeatureWithRepeatedFeature) { // Metrics should only be recorded once per feature. EXPECT_CALL(*client_ptr_, RecordComputedAction).Times(AnyNumber()); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF_Unsupported_feature")); // `HasUnsupportedFeature()` is not called if the viewer is not full-frame. EXPECT_CALL(pdf_service_, HasUnsupportedFeature).Times(0); plugin_->DocumentHasUnsupportedFeature("feature"); plugin_->DocumentHasUnsupportedFeature("feature"); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginFullFrameTest, DocumentHasUnsupportedFeature) { EXPECT_CALL(*client_ptr_, RecordComputedAction).Times(AnyNumber()); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF_Unsupported_feature1")); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF_Unsupported_feature2")); // `HasUnsupportedFeature()` is called once for all features. EXPECT_CALL(pdf_service_, HasUnsupportedFeature); plugin_->DocumentHasUnsupportedFeature("feature1"); plugin_->DocumentHasUnsupportedFeature("feature2"); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginFullFrameTest, DocumentHasUnsupportedFeatureWithRepeatedFeature) { // Metrics should only be recorded once per feature. EXPECT_CALL(*client_ptr_, RecordComputedAction).Times(AnyNumber()); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF_Unsupported_feature")); // `HasUnsupportedFeature()` is called once for all features. EXPECT_CALL(pdf_service_, HasUnsupportedFeature); plugin_->DocumentHasUnsupportedFeature("feature"); plugin_->DocumentHasUnsupportedFeature("feature"); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, DocumentLoadProgress) { EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "loadProgress", "progress": 5.0, })"))); plugin_->DocumentLoadProgress(10, 200); } TEST_F(PdfViewWebPluginTest, DocumentLoadProgressIgnoreSmall) { plugin_->DocumentLoadProgress(2, 100); EXPECT_CALL(*client_ptr_, PostMessage).Times(0); plugin_->DocumentLoadProgress(3, 100); } TEST_F(PdfViewWebPluginTest, DocumentLoadProgressMultipleSmall) { plugin_->DocumentLoadProgress(2, 100); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "loadProgress", "progress": 4.0, })"))); plugin_->DocumentLoadProgress(3, 100); plugin_->DocumentLoadProgress(4, 100); } TEST_F(PdfViewWebPluginTest, EnableAccessibilityBeforeDocumentLoadComplete) { EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); plugin_->EnableAccessibility(); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo); plugin_->CreateUrlLoader(); plugin_->DocumentLoadComplete(); } TEST_F(PdfViewWebPluginTest, EnableAccessibilityBeforeDocumentLoadCompleteRepeated) { EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); plugin_->EnableAccessibility(); plugin_->EnableAccessibility(); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo); plugin_->CreateUrlLoader(); plugin_->DocumentLoadComplete(); } TEST_F(PdfViewWebPluginTest, EnableAccessibilityAfterDocumentLoadComplete) { EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); plugin_->CreateUrlLoader(); plugin_->DocumentLoadComplete(); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo); plugin_->EnableAccessibility(); } TEST_F(PdfViewWebPluginTest, EnableAccessibilityAfterDocumentLoadCompleteRepeated) { plugin_->CreateUrlLoader(); plugin_->DocumentLoadComplete(); plugin_->EnableAccessibility(); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); plugin_->EnableAccessibility(); } TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithNoPermissions) { EXPECT_EQ(kContentRestrictionCopy | kContentRestrictionCut | kContentRestrictionPaste | kContentRestrictionPrint, plugin_->GetContentRestrictions()); EXPECT_FALSE(plugin_->CanCopy()); } TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithCopyAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopy)) .WillRepeatedly(Return(true)); EXPECT_EQ(kContentRestrictionCut | kContentRestrictionPaste | kContentRestrictionPrint, plugin_->GetContentRestrictions()); EXPECT_TRUE(plugin_->CanCopy()); } TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithPrintLowQualityAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); EXPECT_EQ(kContentRestrictionCopy | kContentRestrictionCut | kContentRestrictionPaste, plugin_->GetContentRestrictions()); } TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithCopyAndPrintLowQualityAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopy)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); EXPECT_EQ(kContentRestrictionCut | kContentRestrictionPaste, plugin_->GetContentRestrictions()); } TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithPrintAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintHighQuality)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); EXPECT_EQ(kContentRestrictionCopy | kContentRestrictionCut | kContentRestrictionPaste, plugin_->GetContentRestrictions()); } TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithCopyAndPrintAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopy)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintHighQuality)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); EXPECT_EQ(kContentRestrictionCut | kContentRestrictionPaste, plugin_->GetContentRestrictions()); } TEST_F(PdfViewWebPluginTest, GetAccessibilityDocInfoWithNoPermissions) { AccessibilityDocInfo doc_info = plugin_->GetAccessibilityDocInfo(); EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_FALSE(doc_info.text_accessible); EXPECT_FALSE(doc_info.text_copyable); } TEST_F(PdfViewWebPluginTest, GetAccessibilityDocInfoWithCopyAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopy)) .WillRepeatedly(Return(true)); AccessibilityDocInfo doc_info = plugin_->GetAccessibilityDocInfo(); EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_FALSE(doc_info.text_accessible); EXPECT_TRUE(doc_info.text_copyable); } TEST_F(PdfViewWebPluginTest, GetAccessibilityDocInfoWithCopyAccessibleAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopyAccessible)) .WillRepeatedly(Return(true)); AccessibilityDocInfo doc_info = plugin_->GetAccessibilityDocInfo(); EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_TRUE(doc_info.text_accessible); EXPECT_FALSE(doc_info.text_copyable); } TEST_F(PdfViewWebPluginTest, GetAccessibilityDocInfoWithCopyAndCopyAccessibleAllowed) { EXPECT_CALL(*engine_ptr_, HasPermission).WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopy)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kCopyAccessible)) .WillRepeatedly(Return(true)); AccessibilityDocInfo doc_info = plugin_->GetAccessibilityDocInfo(); EXPECT_EQ(TestPDFiumEngine::kPageNumber, doc_info.page_count); EXPECT_TRUE(doc_info.text_accessible); EXPECT_TRUE(doc_info.text_copyable); } TEST_F(PdfViewWebPluginTest, UpdateGeometrySetsPluginRect) { 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, UpdateGeometrySetsPluginRectOnVariousDeviceScales) { struct UpdateGeometryParams { // The plugin container's device scale. float device_scale; // 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; }; static constexpr UpdateGeometryParams kUpdateGeometryParams[] = { {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, 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_F(PdfViewWebPluginTest, UpdateGeometryScroll) { SetDocumentDimensions({100, 200}); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(4.0f, 6.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(4)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(6)); UpdatePluginGeometryWithoutWaiting(1.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, UpdateGeometryScrollStopped) { SetDocumentDimensions({100, 200}); plugin_->OnMessage(ParseMessage(R"({ "type": "stopScrolling", })")); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(4.0f, 6.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition).Times(0); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition).Times(0); UpdatePluginGeometryWithoutWaiting(1.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, UpdateGeometryScrollUnderflow) { SetDocumentDimensions({100, 200}); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(-1.0f, -1.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(0)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(0)); UpdatePluginGeometryWithoutWaiting(1.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, UpdateGeometryScrollOverflow) { SetDocumentDimensions({100, 200}); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(96.0f, 195.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(95)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(194)); UpdatePluginGeometryWithoutWaiting(1.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, UpdateGeometryScrollOverflowZoomed) { SetDocumentDimensions({100, 200}); SendViewportMessage(/*zoom=*/2.0); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(196.0f, 395.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(195)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(394)); UpdatePluginGeometryWithoutWaiting(1.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, UpdateGeometryScrollScaled) { SetDocumentDimensions({100, 200}); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(4.0f, 6.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(4)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(6)); UpdatePluginGeometryWithoutWaiting(2.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, UpdateGeometryScrollOverflowScaled) { SetDocumentDimensions({100, 200}); EXPECT_CALL(*client_ptr_, GetScrollPosition) .WillRepeatedly(Return(gfx::PointF(195.0f, 395.0f))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(194)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(394)); UpdatePluginGeometryWithoutWaiting(2.0f, gfx::Rect(3, 4, 5, 6)); } TEST_F(PdfViewWebPluginTest, SetCaretPosition) { SetDocumentDimensions({16, 9}); UpdatePluginGeometryWithoutWaiting(1.0f, {10, 20, 20, 5}); EXPECT_CALL(*engine_ptr_, SetCaretPosition(gfx::Point(2, 3))); plugin_->SetCaretPosition({4.0f, 3.0f}); } TEST_F(PdfViewWebPluginTest, SetCaretPositionNegativeOrigin) { SetDocumentDimensions({16, 9}); UpdatePluginGeometryWithoutWaiting(1.0f, {-10, -20, 20, 5}); EXPECT_CALL(*engine_ptr_, SetCaretPosition(gfx::Point(2, 3))); plugin_->SetCaretPosition({4.0f, 3.0f}); } TEST_F(PdfViewWebPluginTest, SetCaretPositionFractional) { SetDocumentDimensions({16, 9}); UpdatePluginGeometryWithoutWaiting(1.0f, {10, 20, 20, 5}); EXPECT_CALL(*engine_ptr_, SetCaretPosition(gfx::Point(1, 2))); plugin_->SetCaretPosition({3.9f, 2.9f}); } TEST_F(PdfViewWebPluginTest, SetCaretPositionScaled) { SetDocumentDimensions({16, 9}); UpdatePluginGeometryWithoutWaiting(2.0f, {20, 40, 40, 10}); EXPECT_CALL(*engine_ptr_, SetCaretPosition(gfx::Point(4, 6))); plugin_->SetCaretPosition({4.0f, 3.0f}); } TEST_F(PdfViewWebPluginTest, 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_F(PdfViewWebPluginTest, 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_F(PdfViewWebPluginTest, 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 : kPaintWithVariousScalesParams) { TestPaintSnapshots(params.device_scale, params.window_rect, params.paint_rect, params.expected_clipped_rect); } } TEST_F(PdfViewWebPluginTest, PaintSnapshotsWithVariousRectPositions) { static constexpr PaintParams kPaintWithVariousPositionsParams[] = { // The window origin falls outside the `paint_rect` area. {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. {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); } } TEST_F(PdfViewWebPluginTest, OnPaintWithMultiplePaintRects) { SetDocumentDimensions({100, 200}); UpdatePluginGeometryWithoutWaiting(/*device_scale=*/1.0f, gfx::Rect(0, 0, 40, 40)); EXPECT_CALL(*engine_ptr_, Paint) .WillRepeatedly( [](const gfx::Rect& rect, SkBitmap& /*image_data*/, std::vector& ready, std::vector& /*pending*/) { ready.push_back(rect); }); std::vector ready; std::vector pending; plugin_->OnPaint( /*paint_rects=*/{gfx::Rect(5, 5, 10, 10), gfx::Rect(20, 20, 10, 10)}, ready, pending); // Expect three paints: an initial background-clearing paint, and one for each // requested paint rectangle. ASSERT_THAT(ready, SizeIs(3)); EXPECT_THAT(pending, IsEmpty()); EXPECT_EQ(gfx::Rect(0, 0, 90, 90), ready[0].rect()); EXPECT_TRUE(ready[0].flush_now()); EXPECT_EQ(gfx::Rect(5, 5, 10, 10), ready[1].rect()); EXPECT_FALSE(ready[1].flush_now()); EXPECT_EQ(gfx::Rect(20, 20, 10, 10), ready[2].rect()); EXPECT_FALSE(ready[2].flush_now()); // All the requested paints should share the same `SkImage`. EXPECT_NE(&ready[0].image(), &ready[1].image()); EXPECT_EQ(&ready[1].image(), &ready[2].image()); } TEST_F(PdfViewWebPluginTest, UpdateLayerTransformWithIdentity) { plugin_->UpdateLayerTransform(1.0f, gfx::Vector2dF()); TestPaintSnapshots(/*device_scale=*/4.0f, /*window_rect=*/gfx::Rect(10, 10, 20, 20), /*paint_rect=*/gfx::Rect(10, 10, 20, 20), /*expected_clipped_rect=*/gfx::Rect(10, 10, 20, 20)); } TEST_F(PdfViewWebPluginTest, UpdateLayerTransformWithScale) { plugin_->UpdateLayerTransform(0.5f, gfx::Vector2dF()); TestPaintSnapshots(/*device_scale=*/4.0f, /*window_rect=*/gfx::Rect(10, 10, 20, 20), /*paint_rect=*/gfx::Rect(10, 10, 20, 20), /*expected_clipped_rect=*/gfx::Rect(10, 10, 10, 10)); } TEST_F(PdfViewWebPluginTest, UpdateLayerTransformWithTranslate) { plugin_->UpdateLayerTransform(1.0f, gfx::Vector2dF(-1.25, 1.25)); TestPaintSnapshots(/*device_scale=*/4.0f, /*window_rect=*/gfx::Rect(10, 10, 20, 20), /*paint_rect=*/gfx::Rect(10, 10, 20, 20), /*expected_clipped_rect=*/gfx::Rect(10, 15, 15, 15)); } TEST_F(PdfViewWebPluginTest, UpdateLayerTransformWithScaleAndTranslate) { plugin_->UpdateLayerTransform(0.5f, gfx::Vector2dF(-1.25, 1.25)); TestPaintSnapshots(/*device_scale=*/4.0f, /*window_rect=*/gfx::Rect(10, 10, 20, 20), /*paint_rect=*/gfx::Rect(10, 10, 20, 20), /*expected_clipped_rect=*/gfx::Rect(10, 15, 5, 10)); } TEST_F(PdfViewWebPluginTest, HandleViewportMessageBeforeDocumentLoadComplete) { EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout(DocumentLayout::Options())); EXPECT_CALL(*client_ptr_, PostMessage).Times(0); plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 0, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": 0, "yOffset": 0, "pinchPhase": 0, })")); } TEST_F(PdfViewWebPluginTest, HandleViewportMessageAfterDocumentLoadComplete) { plugin_->DocumentLoadComplete(); EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout(DocumentLayout::Options())); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "loadProgress", "progress": 100.0, })"))); plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 0, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": 0, "yOffset": 0, "pinchPhase": 0, })")); } TEST_F(PdfViewWebPluginTest, HandleViewportMessageSubsequently) { plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 0, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": 0, "yOffset": 0, "pinchPhase": 0, })")); DocumentLayout::Options two_up_options; two_up_options.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd); EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout(two_up_options)); EXPECT_CALL(*client_ptr_, PostMessage).Times(0); plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 0, "defaultPageOrientation": 0, "twoUpViewEnabled": true, }, "xOffset": 0, "yOffset": 0, "pinchPhase": 0, })")); } TEST_F(PdfViewWebPluginTest, HandleViewportMessageScroll) { EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout) .WillRepeatedly(Return(gfx::Size(16, 9))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(2)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(3)); plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 2, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": 2, "yOffset": 3, "pinchPhase": 0, })")); } TEST_F(PdfViewWebPluginTest, HandleViewportMessageScrollRightToLeft) { EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout) .WillRepeatedly(Return(gfx::Size(16, 9))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(2)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(3)); plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 1, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": 2, "yOffset": 3, "pinchPhase": 0, })")); } TEST_F(PdfViewWebPluginTest, HandleSetBackgroundColorMessage) { ASSERT_NE(SK_ColorGREEN, plugin_->GetBackgroundColor()); base::Value::Dict message; message.Set("type", "setBackgroundColor"); message.Set("color", static_cast(SK_ColorGREEN)); plugin_->OnMessage(message); EXPECT_EQ(SK_ColorGREEN, plugin_->GetBackgroundColor()); } TEST_F(PdfViewWebPluginTest, HandleInputEvent) { UpdatePluginGeometryWithoutWaiting(2.0f, {0, 0, 20, 20}); EXPECT_CALL(*engine_ptr_, HandleInputEvent) .WillRepeatedly([](const blink::WebInputEvent& event) { if (!blink::WebInputEvent::IsMouseEventType(event.GetType())) { ADD_FAILURE() << "Unexpected event type: " << event.GetType(); return false; } const auto& mouse_event = static_cast(event); EXPECT_EQ(blink::WebInputEvent::Type::kMouseDown, mouse_event.GetType()); EXPECT_EQ(gfx::PointF(10.0f, 40.0f), mouse_event.PositionInWidget()); return true; }); blink::WebMouseEvent mouse_event; mouse_event.SetType(blink::WebInputEvent::Type::kMouseDown); mouse_event.SetPositionInWidget(10.0f, 20.0f); ui::Cursor dummy_cursor; EXPECT_EQ(blink::WebInputEventResult::kHandledApplication, plugin_->HandleInputEvent( blink::WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()), &dummy_cursor)); } class PdfViewWebPluginImeTest : public PdfViewWebPluginTest { public: void TestImeSetCompositionForPlugin(const blink::WebString& text) { EXPECT_CALL(*engine_ptr_, 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_ptr_, HandleInputEvent(IsExpectedImeKeyEvent(expected_key))) .WillOnce(Return(true)); } } else { EXPECT_CALL(*engine_ptr_, 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_ptr_, HandleInputEvent(IsExpectedImeKeyEvent(event))) .WillOnce(Return(true)); } } else { EXPECT_CALL(*engine_ptr_, 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, SelectionChanged) { plugin_->EnableAccessibility(); plugin_->DocumentLoadComplete(); UpdatePluginGeometryWithoutWaiting(1.0f, {300, 56, 20, 5}); SetDocumentDimensions({16, 9}); AccessibilityViewportInfo viewport_info; EXPECT_CALL(pdf_service_, SelectionChanged(gfx::PointF(-8.0f, -20.0f), 40, gfx::PointF(52.0f, 60.0f), 80)); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityViewportInfo) .WillOnce(SaveArg<0>(&viewport_info)); plugin_->SelectionChanged({-10, -20, 30, 40}, {50, 60, 70, 80}); EXPECT_EQ(gfx::Point(), viewport_info.scroll); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, SelectionChangedNegativeOrigin) { plugin_->EnableAccessibility(); plugin_->DocumentLoadComplete(); UpdatePluginGeometryWithoutWaiting(1.0f, {-300, -56, 20, 5}); SetDocumentDimensions({16, 9}); AccessibilityViewportInfo viewport_info; EXPECT_CALL(pdf_service_, SelectionChanged(gfx::PointF(-8.0f, -20.0f), 40, gfx::PointF(52.0f, 60.0f), 80)); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityViewportInfo) .WillOnce(SaveArg<0>(&viewport_info)); plugin_->SelectionChanged({-10, -20, 30, 40}, {50, 60, 70, 80}); EXPECT_EQ(gfx::Point(), viewport_info.scroll); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, SelectionChangedScaled) { plugin_->EnableAccessibility(); plugin_->DocumentLoadComplete(); UpdatePluginGeometryWithoutWaiting(2.0f, {600, 112, 40, 10}); SetDocumentDimensions({16, 9}); AccessibilityViewportInfo viewport_info; EXPECT_CALL(pdf_service_, SelectionChanged(gfx::PointF(-8.0f, -20.0f), 40, gfx::PointF(52.0f, 60.0f), 80)); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityViewportInfo) .WillOnce(SaveArg<0>(&viewport_info)); plugin_->SelectionChanged({-20, -40, 60, 80}, {100, 120, 140, 160}); EXPECT_EQ(gfx::Point(), viewport_info.scroll); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, ChangeTextSelection) { ASSERT_FALSE(plugin_->HasSelection()); ASSERT_TRUE(plugin_->SelectionAsText().IsEmpty()); ASSERT_TRUE(plugin_->SelectionAsMarkup().IsEmpty()); static constexpr char kSelectedText[] = "1234"; EXPECT_CALL(*client_ptr_, TextSelectionChanged(blink::WebString::FromUTF8(kSelectedText), 0, gfx::Range(0, 4))); plugin_->SetSelectedText(kSelectedText); EXPECT_TRUE(plugin_->HasSelection()); EXPECT_EQ(kSelectedText, plugin_->SelectionAsText().Utf8()); EXPECT_EQ(kSelectedText, plugin_->SelectionAsMarkup().Utf8()); static constexpr char kEmptyText[] = ""; EXPECT_CALL(*client_ptr_, TextSelectionChanged(blink::WebString::FromUTF8(kEmptyText), 0, gfx::Range(0, 0))); plugin_->SetSelectedText(kEmptyText); EXPECT_FALSE(plugin_->HasSelection()); EXPECT_TRUE(plugin_->SelectionAsText().IsEmpty()); EXPECT_TRUE(plugin_->SelectionAsMarkup().IsEmpty()); } TEST_F(PdfViewWebPluginTest, FormTextFieldFocusChangeUpdatesTextInputType) { ASSERT_EQ(blink::WebTextInputType::kWebTextInputTypeNone, plugin_->GetPluginTextInputType()); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeText); plugin_->FormFieldFocusChange(PDFEngine::FocusFieldType::kText); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); plugin_->FormFieldFocusChange(PDFEngine::FocusFieldType::kNoFocus); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeText); plugin_->FormFieldFocusChange(PDFEngine::FocusFieldType::kText); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); plugin_->FormFieldFocusChange(PDFEngine::FocusFieldType::kNonText); } TEST_F(PdfViewWebPluginTest, SearchString) { static constexpr char16_t kPattern[] = u"fox"; static constexpr char16_t kTarget[] = u"The quick brown fox jumped over the lazy Fox"; { static constexpr PDFEngine::Client::SearchStringResult kExpectation[] = { {16, 3}}; EXPECT_THAT( plugin_->SearchString(kTarget, kPattern, /*case_sensitive=*/true), Pointwise(SearchStringResultEq(), kExpectation)); } { static constexpr PDFEngine::Client::SearchStringResult kExpectation[] = { {16, 3}, {41, 3}}; EXPECT_THAT( plugin_->SearchString(kTarget, kPattern, /*case_sensitive=*/false), Pointwise(SearchStringResultEq(), kExpectation)); } } TEST_F(PdfViewWebPluginTest, UpdateFocus) { MockFunction checkpoint; { InSequence sequence; // Focus false -> true: Triggers updates. EXPECT_CALL(*client_ptr_, UpdateTextInputState); EXPECT_CALL(*client_ptr_, UpdateSelectionBounds); EXPECT_CALL(checkpoint, Call(1)); // Focus true -> true: No updates. EXPECT_CALL(checkpoint, Call(2)); // Focus true -> false: Triggers updates. `UpdateTextInputState` is called // twice because it also gets called due to // `PDFiumEngine::UpdateFocus(false)`. EXPECT_CALL(*client_ptr_, UpdateTextInputState).Times(2); EXPECT_CALL(*client_ptr_, UpdateSelectionBounds); EXPECT_CALL(checkpoint, Call(3)); // Focus false -> false: No updates. EXPECT_CALL(checkpoint, Call(4)); // Focus false -> true: Triggers updates. EXPECT_CALL(*client_ptr_, UpdateTextInputState); EXPECT_CALL(*client_ptr_, UpdateSelectionBounds); } // The focus type does not matter in this test. plugin_->UpdateFocus(/*focused=*/true, blink::mojom::FocusType::kNone); checkpoint.Call(1); plugin_->UpdateFocus(/*focused=*/true, blink::mojom::FocusType::kNone); checkpoint.Call(2); plugin_->UpdateFocus(/*focused=*/false, blink::mojom::FocusType::kNone); checkpoint.Call(3); plugin_->UpdateFocus(/*focused=*/false, blink::mojom::FocusType::kNone); checkpoint.Call(4); plugin_->UpdateFocus(/*focused=*/true, blink::mojom::FocusType::kNone); } TEST_F(PdfViewWebPluginTest, ShouldDispatchImeEventsToPlugin) { ASSERT_TRUE(plugin_->ShouldDispatchImeEventsToPlugin()); } TEST_F(PdfViewWebPluginTest, CaretChange) { 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, EnteredEditMode) { EXPECT_CALL(pdf_service_, SetPluginCanSave(true)); EXPECT_CALL(*client_ptr_, PostMessage).Times(AnyNumber()); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "setIsEditing", })"))); plugin_->EnteredEditMode(); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginTest, NotifyNumberOfFindResultsChanged) { plugin_->StartFind("x", /*case_sensitive=*/false, /*identifier=*/123); const std::vector tickmarks = {gfx::Rect(1, 2), gfx::Rect(3, 4)}; plugin_->UpdateTickMarks(tickmarks); EXPECT_CALL(*client_ptr_, ReportFindInPageTickmarks(tickmarks)); EXPECT_CALL(*client_ptr_, ReportFindInPageMatchCount(123, 5, true)); plugin_->NotifyNumberOfFindResultsChanged(/*total=*/5, /*final_result=*/true); } TEST_F(PdfViewWebPluginTest, OnDocumentLoadComplete) { base::Value::Dict metadata; metadata.Set("fileSize", "0 B"); metadata.Set("linearized", false); metadata.Set("pageSize", "Varies"); metadata.Set("canSerializeDocument", true); base::Value::Dict message; message.Set("type", "metadata"); message.Set("metadataData", std::move(metadata)); EXPECT_CALL(*client_ptr_, PostMessage); EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message)))); plugin_->DocumentLoadComplete(); } class PdfViewWebPluginWithDocInfoTest : public PdfViewWebPluginTest { protected: class TestPDFiumEngineWithDocInfo : public TestPDFiumEngine { public: explicit TestPDFiumEngineWithDocInfo(PDFEngine::Client* client) : TestPDFiumEngine(client) { InitializeDocumentAttachments(); InitializeDocumentMetadata(); } base::Value::List GetBookmarks() override { // Create `bookmark1` which navigates to an in-doc position. This bookmark // will be in the top-level bookmark list. base::Value::Dict bookmark1; bookmark1.Set("title", "Bookmark 1"); bookmark1.Set("page", 2); bookmark1.Set("x", 10); bookmark1.Set("y", 20); bookmark1.Set("zoom", 2.0); // Create `bookmark2` which navigates to a web page. This bookmark will be // a child of `bookmark1`. base::Value::Dict bookmark2; bookmark2.Set("title", "Bookmark 2"); bookmark2.Set("uri", "test.com"); base::Value::List children_of_bookmark1; children_of_bookmark1.Append(std::move(bookmark2)); bookmark1.Set("children", std::move(children_of_bookmark1)); // Create the top-level bookmark list. base::Value::List bookmarks; bookmarks.Append(std::move(bookmark1)); return bookmarks; } absl::optional GetUniformPageSizePoints() override { return gfx::Size(1000, 1200); } private: void InitializeDocumentAttachments() { doc_attachment_info_list().resize(3); // A regular attachment. doc_attachment_info_list()[0].name = u"attachment1.txt"; doc_attachment_info_list()[0].creation_date = u"D:20170712214438-07'00'"; doc_attachment_info_list()[0].modified_date = u"D:20160115091400"; doc_attachment_info_list()[0].is_readable = true; doc_attachment_info_list()[0].size_bytes = 13u; // An unreadable attachment. doc_attachment_info_list()[1].name = u"attachment2.pdf"; doc_attachment_info_list()[1].is_readable = false; // A readable attachment that exceeds download size limit. doc_attachment_info_list()[2].name = u"attachment3.mov"; doc_attachment_info_list()[2].is_readable = true; doc_attachment_info_list()[2].size_bytes = PdfViewPluginBase::kMaximumSavedFileSize + 1; } void InitializeDocumentMetadata() { metadata().version = PdfVersion::k1_7; metadata().size_bytes = 13u; metadata().page_count = 13u; metadata().linearized = true; metadata().has_attachments = true; metadata().form_type = FormType::kAcroForm; metadata().title = "Title"; metadata().author = "Author"; metadata().subject = "Subject"; metadata().keywords = "Keywords"; metadata().creator = "Creator"; metadata().producer = "Producer"; ASSERT_TRUE(base::Time::FromUTCString("2021-05-04 11:12:13", &metadata().creation_date)); ASSERT_TRUE(base::Time::FromUTCString("2021-06-04 15:16:17", &metadata().mod_date)); } }; static base::Value::Dict CreateExpectedAttachmentsResponse() { base::Value::List attachments; { base::Value::Dict attachment; attachment.Set("name", "attachment1.txt"); attachment.Set("size", 13); attachment.Set("readable", true); attachments.Append(std::move(attachment)); } { base::Value::Dict attachment; attachment.Set("name", "attachment2.pdf"); attachment.Set("size", 0); attachment.Set("readable", false); attachments.Append(std::move(attachment)); } { base::Value::Dict attachment; attachment.Set("name", "attachment3.mov"); attachment.Set("size", -1); attachment.Set("readable", true); attachments.Append(std::move(attachment)); } base::Value::Dict message; message.Set("type", "attachments"); message.Set("attachmentsData", std::move(attachments)); return message; } static base::Value::Dict CreateExpectedBookmarksResponse( base::Value::List bookmarks) { base::Value::Dict message; message.Set("type", "bookmarks"); message.Set("bookmarksData", std::move(bookmarks)); return message; } static base::Value::Dict CreateExpectedMetadataResponse() { base::Value::Dict metadata; metadata.Set("version", "1.7"); metadata.Set("fileSize", "13 B"); metadata.Set("linearized", true); metadata.Set("title", "Title"); metadata.Set("author", "Author"); metadata.Set("subject", "Subject"); metadata.Set("keywords", "Keywords"); metadata.Set("creator", "Creator"); metadata.Set("producer", "Producer"); metadata.Set("creationDate", "5/4/21, 4:12:13 AM"); metadata.Set("modDate", "6/4/21, 8:16:17 AM"); metadata.Set("pageSize", "13.89 × 16.67 in (portrait)"); metadata.Set("canSerializeDocument", true); base::Value::Dict message; message.Set("type", "metadata"); message.Set("metadataData", std::move(metadata)); return message; } void SetUpClient() override { EXPECT_CALL(*client_ptr_, CreateEngine).WillOnce([this]() { auto engine = std::make_unique>( plugin_.get()); engine_ptr_ = engine.get(); return engine; }); } }; TEST_F(PdfViewWebPluginWithDocInfoTest, OnDocumentLoadComplete) { const base::Value::Dict expect_attachments = CreateExpectedAttachmentsResponse(); const base::Value::Dict expect_bookmarks = CreateExpectedBookmarksResponse(engine_ptr_->GetBookmarks()); const base::Value::Dict expect_metadata = CreateExpectedMetadataResponse(); EXPECT_CALL(*client_ptr_, PostMessage); EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(expect_attachments)))); EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(expect_bookmarks)))); EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(expect_metadata)))); plugin_->DocumentLoadComplete(); } class PdfViewWebPluginSaveTest : public PdfViewWebPluginTest { protected: static void AddDataToValue(base::span data, base::Value& value) { value.GetDict().Set("dataToSave", base::Value(data)); } void SetUpClient() override { // Ignore non-"saveData" `PdfViewWebPlugin::Client::PostMessage()` calls. EXPECT_CALL(*client_ptr_, PostMessage) .WillRepeatedly([](const base::Value::Dict& message) { EXPECT_NE("saveData", *message.FindString("type")); }); } }; #if BUILDFLAG(ENABLE_INK) TEST_F(PdfViewWebPluginSaveTest, AnnotationInNonEditMode) { base::Value expected_response = base::test::ParseJson(R"({ "type": "saveData", "token": "annotation-in-non-edit-mode", "fileName": "example.pdf", "editModeForTesting": false, })"); AddDataToValue(base::make_span(TestPDFiumEngine::kLoadedData), expected_response); EXPECT_CALL(pdf_service_, SetPluginCanSave(true)); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(expected_response))); plugin_->OnMessage(ParseMessage(R"({ "type": "save", "saveRequestType": 0, "token": "annotation-in-non-edit-mode", })")); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginSaveTest, AnnotationInEditMode) { plugin_->EnteredEditMode(); pdf_receiver_.FlushForTesting(); base::Value expected_response = base::test::ParseJson(R"({ "type": "saveData", "token": "annotation-in-edit-mode", "fileName": "example.pdf", "editModeForTesting": true, })"); AddDataToValue(base::make_span(TestPDFiumEngine::kSaveData), expected_response); EXPECT_CALL(pdf_service_, SetPluginCanSave(true)); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(expected_response))); plugin_->OnMessage(ParseMessage(R"({ "type": "save", "saveRequestType": 0, "token": "annotation-in-edit-mode", })")); pdf_receiver_.FlushForTesting(); } #endif // BUILDFLAG(ENABLE_INK) TEST_F(PdfViewWebPluginSaveTest, OriginalInNonEditMode) { { InSequence pdf_service_sequence; EXPECT_CALL(pdf_service_, SetPluginCanSave(false)); EXPECT_CALL( pdf_service_, SaveUrlAs(GURL(kPdfUrl), network::mojom::ReferrerPolicy::kDefault)); EXPECT_CALL(pdf_service_, SetPluginCanSave(false)); } ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "consumeSaveToken", "token": "original-in-non-edit-mode", })"))); plugin_->OnMessage(ParseMessage(R"({ "type": "save", "saveRequestType": 1, "token": "original-in-non-edit-mode", })")); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginSaveTest, OriginalInEditMode) { plugin_->EnteredEditMode(); pdf_receiver_.FlushForTesting(); { InSequence pdf_service_sequence; EXPECT_CALL(pdf_service_, SetPluginCanSave(false)); EXPECT_CALL( pdf_service_, SaveUrlAs(GURL(kPdfUrl), network::mojom::ReferrerPolicy::kDefault)); EXPECT_CALL(pdf_service_, SetPluginCanSave(true)); } ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "consumeSaveToken", "token": "original-in-edit-mode", })"))); plugin_->OnMessage(ParseMessage(R"({ "type": "save", "saveRequestType": 1, "token": "original-in-edit-mode", })")); pdf_receiver_.FlushForTesting(); } #if BUILDFLAG(ENABLE_INK) TEST_F(PdfViewWebPluginSaveTest, EditedInNonEditMode) { base::Value expected_response = base::test::ParseJson(R"({ "type": "saveData", "token": "edited-in-non-edit-mode", "fileName": "example.pdf", "editModeForTesting": false, })"); AddDataToValue(base::make_span(TestPDFiumEngine::kLoadedData), expected_response); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(expected_response))); plugin_->OnMessage(ParseMessage(R"({ "type": "save", "saveRequestType": 2, "token": "edited-in-non-edit-mode", })")); } #endif // BUILDFLAG(ENABLE_INK) TEST_F(PdfViewWebPluginSaveTest, EditedInEditMode) { plugin_->EnteredEditMode(); base::Value expected_response = base::test::ParseJson(R"({ "type": "saveData", "token": "edited-in-edit-mode", "fileName": "example.pdf", "editModeForTesting": true, })"); AddDataToValue(base::make_span(TestPDFiumEngine::kSaveData), expected_response); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(expected_response))); plugin_->OnMessage(ParseMessage(R"({ "type": "save", "saveRequestType": 2, "token": "edited-in-edit-mode", })")); } class PdfViewWebPluginSubmitFormTest : public PdfViewWebPluginWithoutInitializeTest { protected: void SubmitForm(const std::string& url, base::StringPiece form_data = "data") { EXPECT_TRUE(plugin_->InitializeForTesting()); EXPECT_CALL(*client_ptr_, CreateAssociatedURLLoader).WillOnce([this]() { auto associated_loader = std::make_unique>(); EXPECT_CALL(*associated_loader, LoadAsynchronously) .WillOnce([this](const blink::WebURLRequest& request, blink::WebAssociatedURLLoaderClient* /*client*/) { // TODO(crbug.com/1322928): The `UrlLoader` created by `LoadUrl()` // and `SubmitForm()` shouldn't use different ownership semantics. // The loader created by `SubmitForm()` is owned by the plugin, and // cannot leak past the destruction of the plugin. request_.CopyFrom(request); }); return associated_loader; }); plugin_->SubmitForm(url, form_data.data(), form_data.size()); } void SubmitFailingForm(const std::string& url) { EXPECT_TRUE(plugin_->InitializeForTesting()); EXPECT_CALL(*client_ptr_, CreateAssociatedURLLoader).Times(0); constexpr base::StringPiece kFormData = "form data"; plugin_->SubmitForm(url, kFormData.data(), kFormData.size()); } blink::WebURLRequest request_; }; TEST_F(PdfViewWebPluginSubmitFormTest, RequestMethod) { SetUpPluginWithUrl("https://www.example.com/path/to/the.pdf"); SubmitForm(/*url=*/""); EXPECT_EQ(request_.HttpMethod().Utf8(), "POST"); } TEST_F(PdfViewWebPluginSubmitFormTest, RequestBody) { SetUpPluginWithUrl("https://www.example.com/path/to/the.pdf"); constexpr base::StringPiece kFormData = "form data"; SubmitForm(/*url=*/"", kFormData); blink::WebHTTPBody::Element element; EXPECT_EQ(request_.HttpBody().ElementCount(), 1u); ASSERT_TRUE(request_.HttpBody().ElementAt(0, element)); ASSERT_EQ(element.type, blink::HTTPBodyElementType::kTypeData); EXPECT_THAT(element.data.Copy(), testing::ElementsAreArray(kFormData)); } TEST_F(PdfViewWebPluginSubmitFormTest, RelativeUrl) { SetUpPluginWithUrl("https://www.example.com/path/to/the.pdf"); SubmitForm("relative_endpoint"); EXPECT_EQ(request_.Url().GetString().Utf8(), "https://www.example.com/path/to/relative_endpoint"); } TEST_F(PdfViewWebPluginSubmitFormTest, NoRelativeUrl) { SetUpPluginWithUrl("https://www.example.com/path/to/the.pdf"); SubmitForm(""); EXPECT_EQ(request_.Url().GetString().Utf8(), "https://www.example.com/path/to/the.pdf"); } TEST_F(PdfViewWebPluginSubmitFormTest, AbsoluteUrl) { SetUpPluginWithUrl("https://a.example.com/path/to/the.pdf"); SubmitForm("https://b.example.com/relative_endpoint"); EXPECT_EQ(request_.Url().GetString().Utf8(), "https://b.example.com/relative_endpoint"); } TEST_F(PdfViewWebPluginSubmitFormTest, RelativeUrlInvalidDocumentUrl) { SetUpPluginWithUrl("https://www.%B%Ad.com/path/to/the.pdf"); SubmitFailingForm("relative_endpoint"); } TEST_F(PdfViewWebPluginSubmitFormTest, AbsoluteUrlInvalidDocumentUrl) { SetUpPluginWithUrl("https://www.%B%Ad.com/path/to/the.pdf"); SubmitFailingForm("https://wwww.example.com"); } class PdfViewWebPluginPrintTest : public PdfViewWebPluginTest { protected: void SetUp() override { PdfViewWebPluginTest::SetUp(); // Size must be at least 1 for conversion to `SkMemoryStream`. ON_CALL(*engine_ptr_, PrintPages) .WillByDefault(Return(std::vector(1))); canvas_.sk_canvas()->SetPrintingMetafile(&metafile_); } printing::MetafileSkia metafile_; }; TEST_F(PdfViewWebPluginPrintTest, HighQuality) { EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintHighQuality)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); ASSERT_EQ(static_cast(TestPDFiumEngine::kPageNumber), plugin_->PrintBegin(blink::WebPrintParams())); EXPECT_CALL( *engine_ptr_, PrintPages(ElementsAre(0), Field(&blink::WebPrintParams::rasterize_pdf, IsFalse()))); plugin_->PrintPage(0, canvas_.sk_canvas()); plugin_->PrintEnd(); } TEST_F(PdfViewWebPluginPrintTest, HighQualityRasterized) { EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintHighQuality)) .WillRepeatedly(Return(true)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); blink::WebPrintParams params; params.rasterize_pdf = true; ASSERT_EQ(static_cast(TestPDFiumEngine::kPageNumber), plugin_->PrintBegin(params)); EXPECT_CALL( *engine_ptr_, PrintPages(ElementsAre(0), Field(&blink::WebPrintParams::rasterize_pdf, IsTrue()))); plugin_->PrintPage(0, canvas_.sk_canvas()); plugin_->PrintEnd(); } // Regression test for crbug.com/1307219. TEST_F(PdfViewWebPluginPrintTest, LowQuality) { EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintHighQuality)) .WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); ASSERT_EQ(static_cast(TestPDFiumEngine::kPageNumber), plugin_->PrintBegin(blink::WebPrintParams())); EXPECT_CALL( *engine_ptr_, PrintPages(ElementsAre(0), Field(&blink::WebPrintParams::rasterize_pdf, IsTrue()))); plugin_->PrintPage(0, canvas_.sk_canvas()); plugin_->PrintEnd(); } // Regression test for crbug.com/1307219. TEST_F(PdfViewWebPluginPrintTest, LowQualityRasterized) { EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintHighQuality)) .WillRepeatedly(Return(false)); EXPECT_CALL(*engine_ptr_, HasPermission(DocumentPermission::kPrintLowQuality)) .WillRepeatedly(Return(true)); blink::WebPrintParams params; params.rasterize_pdf = true; ASSERT_EQ(static_cast(TestPDFiumEngine::kPageNumber), plugin_->PrintBegin(params)); EXPECT_CALL( *engine_ptr_, PrintPages(ElementsAre(0), Field(&blink::WebPrintParams::rasterize_pdf, IsTrue()))); plugin_->PrintPage(0, canvas_.sk_canvas()); plugin_->PrintEnd(); } TEST_F(PdfViewWebPluginPrintTest, Disabled) { EXPECT_EQ(0, plugin_->PrintBegin(blink::WebPrintParams())); } TEST_F(PdfViewWebPluginPrintTest, DisabledRasterized) { blink::WebPrintParams params; params.rasterize_pdf = true; EXPECT_EQ(0, plugin_->PrintBegin(params)); } class PdfViewWebPluginPrintPreviewTest : public PdfViewWebPluginTest { protected: void SetUpClient() override { EXPECT_CALL(*client_ptr_, GetEmbedderOriginString) .WillRepeatedly(Return("chrome://print/")); } }; TEST_F(PdfViewWebPluginPrintPreviewTest, HandleResetPrintPreviewModeMessage) { EXPECT_CALL(*client_ptr_, CreateEngine) .WillOnce([](PDFEngine::Client* client, PDFiumFormFiller::ScriptOption script_option) { EXPECT_EQ(PDFiumFormFiller::ScriptOption::kNoJavaScript, script_option); auto engine = std::make_unique>(client); EXPECT_CALL(*engine, ZoomUpdated); EXPECT_CALL(*engine, PageOffsetUpdated); EXPECT_CALL(*engine, PluginSizeUpdated); EXPECT_CALL(*engine, SetGrayscale(false)); return engine; }); plugin_->OnMessage(ParseMessage(R"({ "type": "resetPrintPreviewMode", "url": "chrome-untrusted://print/0/0/print.pdf", "grayscale": false, "pageCount": 1, })")); } TEST_F(PdfViewWebPluginPrintPreviewTest, HandleResetPrintPreviewModeMessageSetGrayscale) { EXPECT_CALL(*client_ptr_, CreateEngine) .WillOnce([](PDFEngine::Client* client, PDFiumFormFiller::ScriptOption /*script_option*/) { auto engine = std::make_unique>(client); EXPECT_CALL(*engine, SetGrayscale(true)); return engine; }); plugin_->OnMessage(ParseMessage(R"({ "type": "resetPrintPreviewMode", "url": "chrome-untrusted://print/0/0/print.pdf", "grayscale": true, "pageCount": 1, })")); } TEST_F(PdfViewWebPluginPrintPreviewTest, DocumentLoadComplete) { plugin_->OnMessage(ParseMessage(R"({ "type": "resetPrintPreviewMode", "url": "chrome-untrusted://print/0/0/print.pdf", "grayscale": false, "pageCount": 1, })")); EXPECT_CALL(*client_ptr_, RecordComputedAction("PDF.LoadSuccess")); EXPECT_CALL(*client_ptr_, PostMessage); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "formFocusChange", "focused": false, })"))); ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "printPreviewLoaded", })"))); EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo) .Times(0); EXPECT_CALL(*client_ptr_, DidStopLoading).Times(0); EXPECT_CALL(pdf_service_, UpdateContentRestrictions).Times(0); plugin_->DocumentLoadComplete(); EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete, plugin_->document_load_state_for_testing()); pdf_receiver_.FlushForTesting(); } TEST_F(PdfViewWebPluginPrintPreviewTest, DocumentLoadProgressResetByResetPrintPreviewModeMessage) { plugin_->DocumentLoadProgress(2, 100); plugin_->OnMessage(ParseMessage(R"({ "type": "resetPrintPreviewMode", "url": "chrome-untrusted://print/123/0/print.pdf", "grayscale": false, "pageCount": 2, })")); EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ "type": "loadProgress", "progress": 3.0, })"))); plugin_->DocumentLoadProgress(3, 100); } TEST_F(PdfViewWebPluginPrintPreviewTest, DocumentLoadProgressNotResetByLoadPreviewPageMessage) { plugin_->OnMessage(ParseMessage(R"({ "type": "resetPrintPreviewMode", "url": "chrome-untrusted://print/123/0/print.pdf", "grayscale": false, "pageCount": 2, })")); plugin_->DocumentLoadProgress(2, 100); plugin_->OnMessage(ParseMessage(R"({ "type": "loadPreviewPage", "url": "chrome-untrusted://print/123/1/print.pdf", "index": 1, })")); EXPECT_CALL(*client_ptr_, PostMessage).Times(0); plugin_->DocumentLoadProgress(3, 100); } TEST_F(PdfViewWebPluginPrintPreviewTest, HandleViewportMessageScrollRightToLeft) { EXPECT_CALL(*engine_ptr_, ApplyDocumentLayout) .WillRepeatedly(Return(gfx::Size(16, 9))); EXPECT_CALL(*engine_ptr_, ScrolledToXPosition(14)); EXPECT_CALL(*engine_ptr_, ScrolledToYPosition(3)); plugin_->OnMessage(ParseMessage(R"({ "type": "viewport", "userInitiated": false, "zoom": 1, "layoutOptions": { "direction": 1, "defaultPageOrientation": 0, "twoUpViewEnabled": false, }, "xOffset": -2, "yOffset": 3, "pinchPhase": 0, })")); } } // namespace chrome_pdf