| // 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 <functional> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #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/buildflags.h" |
| #include "pdf/content_restriction.h" |
| #include "pdf/mojom/pdf.mojom.h" |
| #include "pdf/paint_ready_rect.h" |
| #include "pdf/pdf_view_plugin_base.h" |
| #include "pdf/test/test_helpers.h" |
| #include "pdf/test/test_pdfium_engine.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/input/web_pointer_properties.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_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/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::ElementsAreArray; |
| using ::testing::Eq; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::IsEmpty; |
| using ::testing::MockFunction; |
| using ::testing::NiceMock; |
| using ::testing::Pointwise; |
| using ::testing::Return; |
| using ::testing::SizeIs; |
| |
| // `kCanvasSize` needs to be big enough to hold plugin's snapshots during |
| // 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. |
| 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<const blink::WebKeyboardEvent&>(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<int>(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(const gfx::Rect& expected_clipped_rect, |
| SkColor paint_color) { |
| sk_sp<SkSurface> 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; |
| } |
| |
| 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 MockHeaderVisitor : public blink::WebHTTPHeaderVisitor { |
| public: |
| MOCK_METHOD(void, |
| VisitHeader, |
| (const blink::WebString&, const blink::WebString&), |
| (override)); |
| }; |
| |
| class MockWebAssociatedURLLoader : public blink::WebAssociatedURLLoader { |
| public: |
| MockWebAssociatedURLLoader() { |
| ON_CALL(*this, 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(); |
| }); |
| } |
| |
| // blink::WebAssociatedURLLoader: |
| MOCK_METHOD(void, |
| LoadAsynchronously, |
| (const blink::WebURLRequest&, |
| blink::WebAssociatedURLLoaderClient*), |
| (override)); |
| MOCK_METHOD(void, Cancel, (), (override)); |
| MOCK_METHOD(void, SetDefersLoading, (bool), (override)); |
| MOCK_METHOD(void, |
| SetLoadingTaskRunner, |
| (base::SingleThreadTaskRunner*), |
| (override)); |
| }; |
| |
| class FakePdfViewWebPluginClient : public PdfViewWebPlugin::Client { |
| public: |
| FakePdfViewWebPluginClient() { |
| ON_CALL(*this, CreateAssociatedURLLoader).WillByDefault([]() { |
| return std::make_unique<NiceMock<MockWebAssociatedURLLoader>>(); |
| }); |
| ON_CALL(*this, GetEmbedderOriginString) |
| .WillByDefault( |
| Return("chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/")); |
| ON_CALL(*this, HasFrame).WillByDefault(Return(true)); |
| } |
| |
| // PdfViewWebPlugin::Client: |
| MOCK_METHOD(std::unique_ptr<base::Value>, |
| FromV8Value, |
| (v8::Local<v8::Value>, v8::Local<v8::Context>), |
| (override)); |
| |
| MOCK_METHOD(base::WeakPtr<Client>, GetWeakPtr, (), (override)); |
| |
| MOCK_METHOD(std::unique_ptr<PDFiumEngine>, |
| 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), (override)); |
| |
| MOCK_METHOD(void, |
| ReportFindInPageTickmarks, |
| (const std::vector<gfx::Rect>&), |
| (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<blink::WebAssociatedURLLoader>, |
| 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)); |
| }; |
| |
| class FakePdfService : public pdf::mojom::PdfService { |
| public: |
| MOCK_METHOD(void, |
| SetListener, |
| (mojo::PendingRemote<pdf::mojom::PdfListener>), |
| (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<NiceMock<FakePdfViewWebPluginClient>>(); |
| 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<NiceMock<TestPDFiumEngine>>(client); |
| engine_ptr_ = engine.get(); |
| return engine; |
| }); |
| SetUpClient(); |
| |
| plugin_ = |
| std::unique_ptr<PdfViewWebPlugin, PluginDeleter>(new PdfViewWebPlugin( |
| std::move(client), |
| mojo::AssociatedRemote<pdf::mojom::PdfService>( |
| 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<FakePdfService> pdf_service_; |
| mojo::AssociatedReceiver<pdf::mojom::PdfService> pdf_receiver_{&pdf_service_}; |
| |
| raw_ptr<FakePdfViewWebPluginClient> client_ptr_; |
| std::unique_ptr<PdfViewWebPlugin, PluginDeleter> plugin_; |
| raw_ptr<TestPDFiumEngine> engine_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)); |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "viewport", |
| "userInitiated": false, |
| "zoom": 1, |
| "layoutOptions": { |
| "direction": 2, |
| "defaultPageOrientation": 0, |
| "twoUpViewEnabled": false, |
| }, |
| "xOffset": 0, |
| "yOffset": 0, |
| "pinchPhase": 0, |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| } |
| |
| 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<NiceMock<MockWebAssociatedURLLoader>>(); |
| 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<MockHeaderVisitor> 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, 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, 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, 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(2.0f, gfx::Rect(3, 4, 5, 6)); |
| } |
| |
| 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, SetCaretPositionIgnoresOrigin) { |
| 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, 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<gfx::Rect>& ready, |
| std::vector<gfx::Rect>& /*pending*/) { ready.push_back(rect); }); |
| std::vector<PaintReadyRect> ready; |
| std::vector<gfx::Rect> 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, HandleSetBackgroundColorMessage) { |
| ASSERT_NE(SK_ColorGREEN, plugin_->GetBackgroundColor()); |
| |
| base::Value::Dict message; |
| message.Set("type", "setBackgroundColor"); |
| message.Set("color", static_cast<double>(SK_ColorGREEN)); |
| plugin_->OnMessage(message); |
| |
| EXPECT_EQ(SK_ColorGREEN, plugin_->GetBackgroundColor()); |
| } |
| |
| class PdfViewWebPluginMouseEventsTest : public PdfViewWebPluginTest { |
| protected: |
| 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<blink::WebMouseEvent>(); |
| *scaled_mouse_event_ = static_cast<const blink::WebMouseEvent&>(event); |
| return true; |
| } |
| |
| const blink::WebMouseEvent* GetScaledMouseEvent() const { |
| return scaled_mouse_event_.get(); |
| } |
| |
| private: |
| std::unique_ptr<blink::WebMouseEvent> scaled_mouse_event_; |
| }; |
| |
| void SetUpClient() override { |
| EXPECT_CALL(*client_ptr_, CreateEngine).WillOnce([this]() { |
| auto engine = std::make_unique<NiceMock<TestPDFiumEngineForMouseEvents>>( |
| plugin_.get()); |
| engine_ptr_ = engine.get(); |
| return engine; |
| }); |
| } |
| |
| TestPDFiumEngineForMouseEvents* engine() { |
| return static_cast<TestPDFiumEngineForMouseEvents*>(engine_ptr_); |
| } |
| }; |
| |
| TEST_F(PdfViewWebPluginMouseEventsTest, HandleInputEvent) { |
| EXPECT_CALL(*client_ptr_, DeviceScaleFactor) |
| .WillRepeatedly(Return(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()); |
| } |
| |
| class PdfViewWebPluginImeTest : public PdfViewWebPluginTest { |
| public: |
| void TestImeSetCompositionForPlugin(const blink::WebString& text) { |
| EXPECT_CALL(*engine_ptr_, HandleInputEvent).Times(0); |
| plugin_->ImeSetCompositionForPlugin(text, std::vector<ui::ImeTextSpan>(), |
| 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<ui::ImeTextSpan>(), |
| 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) { |
| 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<void(int checkpoint_num)> 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<gfx::Rect> 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, DocumentLoadCompletePostMessages) { |
| 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<gfx::Size> 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().tagged = 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<NiceMock<TestPDFiumEngineWithDocInfo>>( |
| plugin_.get()); |
| engine_ptr_ = engine.get(); |
| return engine; |
| }); |
| } |
| }; |
| |
| TEST_F(PdfViewWebPluginWithDocInfoTest, DocumentLoadCompletePostMessages) { |
| 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<const uint8_t> data, |
| base::Value& value) { |
| value.SetKey("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))); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "save", |
| "saveRequestType": 0, |
| "token": "annotation-in-non-edit-mode", |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| |
| 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))); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "save", |
| "saveRequestType": 0, |
| "token": "annotation-in-edit-mode", |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| |
| 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", |
| })"))); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "save", |
| "saveRequestType": 1, |
| "token": "original-in-non-edit-mode", |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| |
| 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", |
| })"))); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "save", |
| "saveRequestType": 1, |
| "token": "original-in-edit-mode", |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| |
| 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))); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "save", |
| "saveRequestType": 2, |
| "token": "edited-in-non-edit-mode", |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| } |
| #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))); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "save", |
| "saveRequestType": 2, |
| "token": "edited-in-edit-mode", |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| } |
| |
| 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<NiceMock<MockWebAssociatedURLLoader>>(); |
| 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 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<NiceMock<TestPDFiumEngine>>(client); |
| EXPECT_CALL(*engine, ZoomUpdated); |
| EXPECT_CALL(*engine, PageOffsetUpdated); |
| EXPECT_CALL(*engine, PluginSizeUpdated); |
| EXPECT_CALL(*engine, SetGrayscale(false)); |
| return engine; |
| }); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "resetPrintPreviewMode", |
| "url": "chrome-untrusted://print/0/0/print.pdf", |
| "grayscale": false, |
| "pageCount": 1, |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| } |
| |
| TEST_F(PdfViewWebPluginPrintPreviewTest, |
| HandleResetPrintPreviewModeMessageSetGrayscale) { |
| EXPECT_CALL(*client_ptr_, CreateEngine) |
| .WillOnce([](PDFEngine::Client* client, |
| PDFiumFormFiller::ScriptOption /*script_option*/) { |
| auto engine = std::make_unique<NiceMock<TestPDFiumEngine>>(client); |
| EXPECT_CALL(*engine, SetGrayscale(true)); |
| return engine; |
| }); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "resetPrintPreviewMode", |
| "url": "chrome-untrusted://print/0/0/print.pdf", |
| "grayscale": true, |
| "pageCount": 1, |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| } |
| |
| TEST_F(PdfViewWebPluginPrintPreviewTest, |
| DocumentLoadProgressResetByResetPrintPreviewModeMessage) { |
| plugin_->DocumentLoadProgress(2, 100); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "resetPrintPreviewMode", |
| "url": "chrome-untrusted://print/123/0/print.pdf", |
| "grayscale": false, |
| "pageCount": 2, |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| |
| EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({ |
| "type": "loadProgress", |
| "progress": 3.0, |
| })"))); |
| plugin_->DocumentLoadProgress(3, 100); |
| } |
| |
| TEST_F(PdfViewWebPluginPrintPreviewTest, |
| DocumentLoadProgressNotResetByLoadPreviewPageMessage) { |
| base::Value reset_message = base::test::ParseJson(R"({ |
| "type": "resetPrintPreviewMode", |
| "url": "chrome-untrusted://print/123/0/print.pdf", |
| "grayscale": false, |
| "pageCount": 2, |
| })"); |
| plugin_->OnMessage(reset_message.GetDict()); |
| |
| plugin_->DocumentLoadProgress(2, 100); |
| |
| base::Value load_page_message = base::test::ParseJson(R"({ |
| "type": "loadPreviewPage", |
| "url": "chrome-untrusted://print/123/1/print.pdf", |
| "index": 1, |
| })"); |
| plugin_->OnMessage(load_page_message.GetDict()); |
| |
| 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)); |
| |
| base::Value message = base::test::ParseJson(R"({ |
| "type": "viewport", |
| "userInitiated": false, |
| "zoom": 1, |
| "layoutOptions": { |
| "direction": 1, |
| "defaultPageOrientation": 0, |
| "twoUpViewEnabled": false, |
| }, |
| "xOffset": -2, |
| "yOffset": 3, |
| "pinchPhase": 0, |
| })"); |
| plugin_->OnMessage(message.GetDict()); |
| } |
| |
| } // namespace chrome_pdf |