0
Files
src/pdf/pdf_view_web_plugin_unittest.cc
Alan Screen 3977e99b1d Use base::FilePath::StringType for GetInkTestDataFilePath()
Update GetInkTestDataFilePath() to accept the platform-varying
base::FilePath::StringType as the filename parameter type.  This
makes the method more usable for filename manipulation, such as
with GetTestDataPathWithPlatformSuffix().

Change-Id: I7c1b6307d4fa6fb8f49d10f8986d82be6665c3a6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6353336
Reviewed-by: Andy Phan <andyphan@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1432397}
2025-03-13 15:01:00 -07:00

3140 lines
112 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2021 The Chromium Authors
// 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 <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#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/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.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_accessibility_image_fetcher.h"
#include "pdf/pdf_features.h"
#include "pdf/test/mock_web_associated_url_loader.h"
#include "pdf/test/mouse_event_builder.h"
#include "pdf/test/test_helpers.h"
#include "pdf/test/test_pdfium_engine.h"
#include "printing/metafile_skia.h"
#include "printing/units.h"
#include "services/network/public/mojom/referrer_policy.mojom-shared.h"
#include "services/screen_ai/buildflags/buildflags.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"
#if BUILDFLAG(ENABLE_PDF_INK2)
#include "pdf/pdf_ink_brush.h"
#include "pdf/pdf_ink_metrics_handler.h"
#include "pdf/pdf_ink_module_client.h"
#include "pdf/test/pdf_ink_test_helpers.h"
#include "pdf/test/test_helpers.h"
#include "third_party/ink/src/ink/strokes/stroke.h"
#endif
using printing::kUnitConversionFactorPixelsToPoints;
namespace chrome_pdf {
namespace {
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::IsEmpty;
using ::testing::IsFalse;
using ::testing::IsTrue;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Pair;
using ::testing::Pointwise;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SizeIs;
#if BUILDFLAG(ENABLE_PDF_INK2)
constexpr char kPdfLoadedWithV2InkAnnotationsMetric[] =
"PDF.LoadedWithV2InkAnnotations2";
#endif // BUILDFLAG(ENABLE_PDF_INK2)
// `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;
// This constant should have the same value as the one in
// `pdf_view_web_plugin.cc`.
// LINT.IfChange(searchify_state_propagation_delay)
constexpr base::TimeDelta kSearchifyStatePropagationDelay = base::Seconds(1);
// LINT.ThenChange(//pdf/pdf_view_web_plugin.cc:searchify_state_propagation_delay)
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, "") {
PDFiumEngineClient::SearchStringResult l = std::get<0>(arg);
PDFiumEngineClient::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.data() == expected_text &&
event.unmodified_text.data() == expected_text;
}
base::Value::Dict ParseMessage(std::string_view 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<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;
}
base::Value::Dict GenerateShowSearchifyInProgressMessage(bool show) {
base::Value::Dict message;
message.Set("type", "showSearchifyInProgress");
message.Set("show", show);
return message;
}
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<AccessibilityTextRunInfo>,
std::vector<AccessibilityCharInfo>,
AccessibilityPageObjects),
(override));
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
MOCK_METHOD(void, OnHasSearchifyText, (), (override));
#endif
};
class FakePdfViewWebPluginClient : public PdfViewWebPlugin::Client {
public:
FakePdfViewWebPluginClient() {
ON_CALL(*this, CreateAssociatedURLLoader).WillByDefault([]() {
auto associated_loader =
std::make_unique<NiceMock<MockWebAssociatedURLLoader>>();
ON_CALL(*associated_loader, LoadAsynchronously)
.WillByDefault([](const blink::WebURLRequest& /*request*/,
blink::WebAssociatedURLLoaderClient* client) {
// TODO(crbug.com/40224475): Must trigger callback to free
// `UrlLoader`.
client->DidReceiveResponse(blink::WebURLResponse());
client->DidFinishLoading();
});
return associated_loader;
});
ON_CALL(*this, GetIsolate).WillByDefault(Return(GetBlinkIsolate()));
ON_CALL(*this, DeviceScaleFactor).WillByDefault(Return(1.0f));
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,
(PDFiumEngineClient*, PDFiumFormFiller::ScriptOption),
(override));
MOCK_METHOD(void,
SetPluginContainer,
(blink::WebPluginContainer*),
(override));
MOCK_METHOD(blink::WebPluginContainer*, PluginContainer, (), (override));
MOCK_METHOD(v8::Isolate*, GetIsolate, (), (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<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));
MOCK_METHOD(std::unique_ptr<PdfAccessibilityDataHandler>,
CreateAccessibilityDataHandler,
(PdfAccessibilityActionHandler*,
PdfAccessibilityImageFetcher*,
blink::WebPluginContainer*,
bool),
(override));
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
MOCK_METHOD(void,
PerformOcr,
(const SkBitmap& image,
base::OnceCallback<void(screen_ai::mojom::VisualAnnotationPtr)>
callback),
(override));
MOCK_METHOD(void,
SetOcrDisconnectedCallback,
(base::RepeatingClosure callback),
(override));
#endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
};
class FakePdfHost : public pdf::mojom::PdfHost {
public:
MOCK_METHOD(void,
SetListener,
(mojo::PendingRemote<pdf::mojom::PdfListener>),
(override));
MOCK_METHOD(void, OnDocumentLoadComplete, (), (override));
MOCK_METHOD(void, UpdateContentRestrictions, (int32_t), (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));
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
MOCK_METHOD(void, OnSearchifyStarted, (), (override));
#endif
};
} // namespace
class PdfViewWebPluginWithoutInitializeTest
: public testing::TestWithParam<bool> {
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(std::string_view name,
std::string_view value,
blink::WebPluginParams& params) {
params.attribute_names.push_back(blink::WebString::FromUTF8(name));
params.attribute_values.push_back(blink::WebString::FromUTF8(value));
}
void SetUpPlugin(std::string_view document_url,
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](
PDFiumEngineClient* client,
PDFiumFormFiller::ScriptOption /*script_option*/) {
auto engine = std::make_unique<NiceMock<TestPDFiumEngine>>(client);
engine_ptr_ = engine.get();
return engine;
});
ON_CALL(*client_ptr_, CreateAccessibilityDataHandler)
.WillByDefault([this]() {
auto handler =
std::make_unique<NiceMock<MockPdfAccessibilityDataHandler>>();
accessibility_data_handler_ptr_ = handler.get();
return handler;
});
SetUpClient();
plugin_ =
std::unique_ptr<PdfViewWebPlugin, PluginDeleter>(new PdfViewWebPlugin(
std::move(client),
mojo::AssociatedRemote<pdf::mojom::PdfHost>(
pdf_receiver_.BindNewEndpointAndPassDedicatedRemote()),
std::move(params)));
}
void SetUpPluginWithUrl(const std::string& url) {
blink::WebPluginParams params;
AddToPluginParams("src", url, params);
SetUpPluginParams(params);
SetUpPlugin(url, std::move(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 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());
});
}
void OnMessageWithEngineUpdate(const base::Value::Dict& message) {
// New engine will be created making this unowned reference stale.
engine_ptr_ = nullptr;
plugin_->OnMessage(message);
}
NiceMock<FakePdfHost> pdf_host_;
mojo::AssociatedReceiver<pdf::mojom::PdfHost> pdf_receiver_{&pdf_host_};
// Must outlive raw_ptrs below.
std::unique_ptr<PdfViewWebPlugin, PluginDeleter> plugin_;
raw_ptr<FakePdfViewWebPluginClient> client_ptr_;
raw_ptr<TestPDFiumEngine> engine_ptr_;
raw_ptr<MockPdfAccessibilityDataHandler> 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::SingleThreadTaskRunner::GetCurrentDefault()->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()))
<< "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()))
<< "Failure at device scale of " << device_scale << ", window rect of "
<< window_rect.ToString();
}
ui::Cursor TestSendInputEvent(const blink::WebInputEvent& event,
blink::WebInputEventResult expected_result) {
ui::Cursor cursor;
EXPECT_EQ(
expected_result,
plugin_->HandleInputEvent(
blink::WebCoalescedInputEvent(event, ui::LatencyInfo()), &cursor));
return cursor;
}
// 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_host_, UpdateContentRestrictions).Times(0);
plugin_->CreateUrlLoader();
EXPECT_EQ(PdfViewWebPlugin::DocumentLoadState::kLoading,
plugin_->document_load_state_for_testing());
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginFullFrameTest, CreateUrlLoader) {
EXPECT_CALL(*client_ptr_, DidStartLoading);
EXPECT_CALL(pdf_host_, UpdateContentRestrictions(kContentRestrictionSave |
kContentRestrictionPrint));
plugin_->CreateUrlLoader();
EXPECT_EQ(PdfViewWebPlugin::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": "none",
})")));
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_host_, UpdateContentRestrictions).Times(0);
plugin_->DocumentLoadComplete();
EXPECT_EQ(PdfViewWebPlugin::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": "none",
})")));
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_host_, UpdateContentRestrictions(kContentRestrictionPrint |
kContentRestrictionPaste |
kContentRestrictionCut |
kContentRestrictionCopy));
EXPECT_CALL(pdf_host_, OnDocumentLoadComplete);
plugin_->DocumentLoadComplete();
EXPECT_EQ(PdfViewWebPlugin::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);
EXPECT_CALL(pdf_host_, OnDocumentLoadComplete).Times(0);
plugin_->DocumentLoadFailed();
EXPECT_EQ(PdfViewWebPlugin::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(PdfViewWebPlugin::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"));
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"));
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"));
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"));
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,
LoadOrReloadAccessibilityBeforeDocumentLoadComplete) {
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo)
.Times(0);
plugin_->LoadOrReloadAccessibility();
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo);
plugin_->CreateUrlLoader();
plugin_->DocumentLoadComplete();
}
TEST_F(PdfViewWebPluginTest,
LoadOrReloadAccessibilityBeforeDocumentLoadCompleteRepeated) {
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo)
.Times(0);
plugin_->LoadOrReloadAccessibility();
plugin_->LoadOrReloadAccessibility();
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo);
plugin_->CreateUrlLoader();
plugin_->DocumentLoadComplete();
}
TEST_F(PdfViewWebPluginTest,
LoadOrReloadAccessibilityAfterDocumentLoadComplete) {
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo)
.Times(0);
plugin_->CreateUrlLoader();
plugin_->DocumentLoadComplete();
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo);
plugin_->LoadOrReloadAccessibility();
}
TEST_F(PdfViewWebPluginTest,
LoadOrReloadAccessibilityAfterDocumentLoadCompleteRepeated) {
plugin_->CreateUrlLoader();
plugin_->DocumentLoadComplete();
plugin_->LoadOrReloadAccessibility();
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo);
plugin_->LoadOrReloadAccessibility();
}
TEST_F(PdfViewWebPluginTest,
LoadOrReloadAccessibilityResetsAccessibilityPageIndex) {
plugin_->CreateUrlLoader();
plugin_->DocumentLoadComplete();
plugin_->LoadOrReloadAccessibility();
EXPECT_EQ(plugin_->next_accessibility_page_index_for_testing(), 0);
plugin_->set_next_accessibility_page_index_for_testing(5);
EXPECT_CALL(*accessibility_data_handler_ptr_, SetAccessibilityDocInfo);
plugin_->LoadOrReloadAccessibility();
EXPECT_EQ(plugin_->next_accessibility_page_index_for_testing(), 0);
}
TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithNoPermissions) {
EXPECT_EQ(kContentRestrictionCopy | kContentRestrictionCut |
kContentRestrictionPaste | kContentRestrictionPrint,
plugin_->GetContentRestrictionsForTesting());
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_->GetContentRestrictionsForTesting());
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_->GetContentRestrictionsForTesting());
}
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_->GetContentRestrictionsForTesting());
}
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_->GetContentRestrictionsForTesting());
}
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_->GetContentRestrictionsForTesting());
}
TEST_F(PdfViewWebPluginTest, GetAccessibilityDocInfoWithNoPermissions) {
AccessibilityDocInfo doc_info = plugin_->GetAccessibilityDocInfoForTesting();
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_->GetAccessibilityDocInfoForTesting();
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_->GetAccessibilityDocInfoForTesting();
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_->GetAccessibilityDocInfoForTesting();
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<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, 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<double>(SK_ColorGREEN));
plugin_->OnMessage(message);
EXPECT_EQ(SK_ColorGREEN, plugin_->GetBackgroundColor());
}
TEST_F(PdfViewWebPluginTest, HandleSetPresentationModeMessage) {
EXPECT_FALSE(engine_ptr_->IsReadOnly());
plugin_->set_cursor_type_for_testing(ui::mojom::CursorType::kIBeam);
base::Value::Dict message;
message.Set("type", "setPresentationMode");
message.Set("enablePresentationMode", true);
plugin_->OnMessage(message);
// After entering presentation mode, PDFiumEngine is read-only and the cursor
// type has been reset to a pointer.
EXPECT_TRUE(engine_ptr_->IsReadOnly());
EXPECT_EQ(ui::mojom::CursorType::kPointer,
plugin_->cursor_for_testing().type());
message.Set("enablePresentationMode", false);
plugin_->OnMessage(message);
// After exiting presentation mode, PDFiumEngine is no longer read-only.
// The cursor remains as a pointer until the next input event updates it.
EXPECT_FALSE(engine_ptr_->IsReadOnly());
EXPECT_EQ(ui::mojom::CursorType::kPointer,
plugin_->cursor_for_testing().type());
}
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<const blink::WebMouseEvent&>(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);
TestSendInputEvent(mouse_event,
blink::WebInputEventResult::kHandledApplication);
}
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) {
std::u16string_view 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) {
std::u16string_view 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, SelectionChanged) {
plugin_->EnableAccessibility();
plugin_->DocumentLoadComplete();
UpdatePluginGeometryWithoutWaiting(1.0f, {300, 56, 20, 5});
SetDocumentDimensions({16, 9});
AccessibilityViewportInfo viewport_info;
EXPECT_CALL(pdf_host_, 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_host_, 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_host_, 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, SelectAll) {
EXPECT_CALL(*engine_ptr_, SelectAll);
EXPECT_TRUE(plugin_->ExecuteEditCommand(
/*name=*/blink::WebString::FromASCII("SelectAll"),
/*value=*/blink::WebString()));
}
TEST_F(PdfViewWebPluginTest, FormTextFieldFocusChangeUpdatesTextInputType) {
ASSERT_EQ(blink::WebTextInputType::kWebTextInputTypeNone,
plugin_->GetPluginTextInputType());
ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeText);
plugin_->FormFieldFocusChange(PDFiumEngineClient::FocusFieldType::kText);
ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone);
plugin_->FormFieldFocusChange(PDFiumEngineClient::FocusFieldType::kNoFocus);
ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeText);
plugin_->FormFieldFocusChange(PDFiumEngineClient::FocusFieldType::kText);
ExpectUpdateTextInputState(blink::WebTextInputType::kWebTextInputTypeNone);
plugin_->FormFieldFocusChange(PDFiumEngineClient::FocusFieldType::kNonText);
}
TEST_F(PdfViewWebPluginTest, SearchString) {
static constexpr char16_t kNeedle[] = u"fox";
static constexpr char16_t kHaystack[] =
u"The quick brown fox jumped over the lazy Fox";
{
static constexpr PDFiumEngineClient::SearchStringResult kExpectation[] = {
{16, 3}};
EXPECT_THAT(
plugin_->SearchString(kNeedle, kHaystack, /*case_sensitive=*/true),
Pointwise(SearchStringResultEq(), kExpectation));
}
{
static constexpr PDFiumEngineClient::SearchStringResult kExpectation[] = {
{16, 3}, {41, 3}};
EXPECT_THAT(
plugin_->SearchString(kNeedle, kHaystack, /*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_host_, 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, 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();
}
TEST_F(PdfViewWebPluginTest, OnSearchifyStarted) {
base::Value::Dict message = GenerateShowSearchifyInProgressMessage(true);
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message))));
plugin_->OnSearchifyStateChange(true);
EXPECT_CALL(pdf_host_, OnSearchifyStarted);
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginTest, OnSearchifyStartedAndStoppedFast) {
base::Value::Dict message_show = GenerateShowSearchifyInProgressMessage(true);
base::Value::Dict message_hide =
GenerateShowSearchifyInProgressMessage(false);
// Since "hide" message is sent with some delay, it is expected to only
// observe one "show" message.
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message_show))));
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message_hide)))).Times(0);
plugin_->OnSearchifyStateChange(true);
plugin_->OnSearchifyStateChange(false);
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginTest, OnSearchifyStopped) {
base::Value::Dict message_show = GenerateShowSearchifyInProgressMessage(true);
base::Value::Dict message_hide =
GenerateShowSearchifyInProgressMessage(false);
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message_show))));
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message_hide))));
plugin_->OnSearchifyStateChange(true);
plugin_->OnSearchifyStateChange(false);
// Wait for the state to be propagated and indicator be hidden.
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), kSearchifyStatePropagationDelay);
run_loop.Run();
}
TEST_F(PdfViewWebPluginTest, OnSearchifyStartedAndStoppedAndStarted) {
base::Value::Dict message_show = GenerateShowSearchifyInProgressMessage(true);
base::Value::Dict message_hide =
GenerateShowSearchifyInProgressMessage(false);
// Since `PdfViewWebPlugin` does not keep two distinct states for "progress
// indicator is showing" and "progress indicator will be hidden a bit later",
// an unnecessary show message is sent to UI. This has no effect in the UI as
// the progress indicator is already showing.
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message_show)))).Times(2);
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message_hide)))).Times(0);
plugin_->OnSearchifyStateChange(true);
plugin_->OnSearchifyStateChange(false);
// Wait, but not enough for the state to be propagated.
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), kSearchifyStatePropagationDelay / 2);
run_loop.Run();
plugin_->OnSearchifyStateChange(true);
}
TEST_F(PdfViewWebPluginTest, OnSearchifyStartedMoreThanOnce) {
plugin_->OnSearchifyStateChange(true);
plugin_->OnSearchifyStateChange(false);
plugin_->OnSearchifyStateChange(true);
EXPECT_CALL(pdf_host_, OnSearchifyStarted);
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginTest, OnHasSearchifyText) {
base::Value::Dict message;
message.Set("type", "setHasSearchifyText");
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(message))));
plugin_->OnHasSearchifyText();
}
TEST_F(PdfViewWebPluginTest, HighlightTextFragments) {
base::Value::List fragments;
fragments.Append("hello-,world");
fragments.Append("world,-hello");
base::Value::Dict message;
message.Set("type", "highlightTextFragments");
message.Set("textFragments", std::move(fragments));
EXPECT_CALL(*engine_ptr_, HighlightTextFragments(
ElementsAre("hello-,world", "world,-hello")));
plugin_->OnMessage(message);
}
class PdfViewWebPluginWithDocInfoTest : public PdfViewWebPluginTest {
public:
void SetUp() override {
PdfViewWebPluginTest::SetUp();
if (IsPortfolioEnabled()) {
scoped_feature_list_.InitAndEnableFeature(features::kPdfPortfolio);
}
}
bool IsPortfolioEnabled() { return GetParam(); }
protected:
class TestPDFiumEngineWithDocInfo : public TestPDFiumEngine {
public:
explicit TestPDFiumEngineWithDocInfo(PDFiumEngineClient* 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;
}
std::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 =
PdfViewWebPlugin::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\xE2\x80\xAF"
"AM");
metadata.Set("modDate",
"6/4/21, 8:16:17\xE2\x80\xAF"
"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;
});
}
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(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))))
.Times(IsPortfolioEnabled() ? 1 : 0);
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(expect_bookmarks))));
EXPECT_CALL(*client_ptr_, PostMessage(Eq(std::ref(expect_metadata))));
plugin_->DocumentLoadComplete();
}
INSTANTIATE_TEST_SUITE_P(All, PdfViewWebPluginWithDocInfoTest, testing::Bool());
class PdfViewWebPluginSaveTest : public PdfViewWebPluginTest {
protected:
static void AddDataToValue(base::span<const uint8_t> 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::span(TestPDFiumEngine::kLoadedData), expected_response);
EXPECT_CALL(pdf_host_, 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) {
EXPECT_CALL(pdf_host_, SetPluginCanSave(true));
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::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": 0,
"token": "annotation-in-edit-mode",
})"));
pdf_receiver_.FlushForTesting();
}
#endif // BUILDFLAG(ENABLE_INK)
TEST_F(PdfViewWebPluginSaveTest, OriginalInNonEditMode) {
{
InSequence pdf_host_sequence;
EXPECT_CALL(pdf_host_, SaveUrlAs(GURL(kPdfUrl),
network::mojom::ReferrerPolicy::kDefault));
}
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_host_sequence;
EXPECT_CALL(pdf_host_, SetPluginCanSave(false));
EXPECT_CALL(pdf_host_, SaveUrlAs(GURL(kPdfUrl),
network::mojom::ReferrerPolicy::kDefault));
EXPECT_CALL(pdf_host_, 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::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::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, std::string_view 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/40224475): 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 std::string_view 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 std::string_view 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<uint8_t>(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<int>(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<int>(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<int>(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<int>(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([](PDFiumEngineClient* 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;
});
OnMessageWithEngineUpdate(ParseMessage(R"({
"type": "resetPrintPreviewMode",
"url": "chrome-untrusted://print/0/0/print.pdf",
"grayscale": false,
"pageCount": 1,
})"));
}
TEST_F(PdfViewWebPluginPrintPreviewTest,
HandleResetPrintPreviewModeMessageForPdf) {
EXPECT_CALL(*client_ptr_, CreateEngine)
.WillOnce([](PDFiumEngineClient* client,
PDFiumFormFiller::ScriptOption script_option) {
EXPECT_EQ(PDFiumFormFiller::ScriptOption::kNoJavaScript, script_option);
return std::make_unique<NiceMock<TestPDFiumEngine>>(client);
});
// The UI ID of 1 in the URL is arbitrary.
// The page index value of -1, AKA `kCompletePDFIndex`, is required for PDFs.
OnMessageWithEngineUpdate(ParseMessage(R"({
"type": "resetPrintPreviewMode",
"url": "chrome-untrusted://print/1/-1/print.pdf",
"grayscale": false,
"pageCount": 0,
})"));
EXPECT_CALL(*client_ptr_, PostMessage).Times(AnyNumber());
EXPECT_CALL(*client_ptr_, PostMessage(base::test::IsJson(R"({
"type": "printPreviewLoaded",
})")));
plugin_->DocumentLoadComplete();
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginPrintPreviewTest,
HandleResetPrintPreviewModeMessageSetGrayscale) {
EXPECT_CALL(*client_ptr_, CreateEngine)
.WillOnce([](PDFiumEngineClient* client,
PDFiumFormFiller::ScriptOption /*script_option*/) {
auto engine = std::make_unique<NiceMock<TestPDFiumEngine>>(client);
EXPECT_CALL(*engine, SetGrayscale(true));
return engine;
});
OnMessageWithEngineUpdate(ParseMessage(R"({
"type": "resetPrintPreviewMode",
"url": "chrome-untrusted://print/0/0/print.pdf",
"grayscale": true,
"pageCount": 1,
})"));
}
TEST_F(PdfViewWebPluginPrintPreviewTest, DocumentLoadComplete) {
OnMessageWithEngineUpdate(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": "none",
})")));
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_host_, UpdateContentRestrictions).Times(0);
plugin_->DocumentLoadComplete();
EXPECT_EQ(PdfViewWebPlugin::DocumentLoadState::kComplete,
plugin_->document_load_state_for_testing());
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginPrintPreviewTest,
DocumentLoadProgressResetByResetPrintPreviewModeMessage) {
plugin_->DocumentLoadProgress(2, 100);
OnMessageWithEngineUpdate(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) {
OnMessageWithEngineUpdate(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,
})"));
}
#if BUILDFLAG(ENABLE_PDF_INK2)
class PdfViewWebPluginInkTest : public PdfViewWebPluginTest {
protected:
void SetUpWithTrivialInkStrokes() {
// Set up the engine so the plugin can draw strokes. The exact strokes do
// not matter.
EXPECT_CALL(*engine_ptr_, HandleInputEvent).Times(0);
ON_CALL(*engine_ptr_, GetPageContentsRect)
.WillByDefault(
Return(gfx::Rect(/*x=*/0, /*y=*/0, /*width=*/100, /*height=*/50)));
ON_CALL(*engine_ptr_, GetPageSizeInPoints)
.WillByDefault(Return(gfx::SizeF(75.0f, 37.5f)));
ON_CALL(*engine_ptr_, GetThumbnailSize)
.WillByDefault(Return(gfx::Size(50, 25)));
ON_CALL(*engine_ptr_, IsPageVisible).WillByDefault(Return(true));
// Draw some trivial strokes.
plugin_->OnMessage(
CreateSetAnnotationModeMessageForTesting(/*enable=*/true));
TestSendInputEvent(
MouseEventBuilder().CreateLeftClickAtPosition({10, 10}).Build(),
blink::WebInputEventResult::kHandledApplication);
TestSendInputEvent(
MouseEventBuilder().CreateLeftMouseUpAtPosition({20, 20}).Build(),
blink::WebInputEventResult::kHandledApplication);
}
void SendThumbnail(std::string_view message_id, const gfx::SizeF& page_size) {
base::Value::Dict reply;
reply.Set("type", "getThumbnailReply");
reply.Set("messageId", message_id);
plugin_->SendThumbnailForTesting(
std::move(reply), /*page_index=*/0,
Thumbnail(page_size, /*device_pixel_ratio=*/1));
}
private:
base::test::ScopedFeatureList feature_list_{features::kPdfInk2};
};
TEST_F(PdfViewWebPluginInkTest, Invalidate) {
plugin_->set_in_paint_for_testing(true);
EXPECT_EQ(0u, plugin_->deferred_invalidates_for_testing().size());
SetUpWithTrivialInkStrokes();
EXPECT_EQ(3u, plugin_->deferred_invalidates_for_testing().size());
}
TEST_F(PdfViewWebPluginInkTest, LoadV2InkPathsForPageAndUpdateShapeActive) {
const std::map<InkModeledShapeId, ink::PartitionedMesh> kEmptyMap;
const std::map<InkModeledShapeId, ink::PartitionedMesh> kMap0{
{InkModeledShapeId(0), ink::PartitionedMesh()},
};
const std::map<InkModeledShapeId, ink::PartitionedMesh> kMap1{
{InkModeledShapeId(1), ink::PartitionedMesh()},
{InkModeledShapeId(2), ink::PartitionedMesh()},
};
const std::map<InkModeledShapeId, ink::PartitionedMesh> kMap2{
{InkModeledShapeId(3), ink::PartitionedMesh()},
{InkModeledShapeId(4), ink::PartitionedMesh()},
{InkModeledShapeId(5), ink::PartitionedMesh()},
};
EXPECT_CALL(*engine_ptr_, LoadV2InkPathsForPage(testing::Lt(12)))
.Times(12)
.WillOnce(Return(kMap0))
.WillOnce(Return(kMap1))
.WillRepeatedly(Return(kEmptyMap));
EXPECT_CALL(*engine_ptr_, LoadV2InkPathsForPage(12)).WillOnce(Return(kMap2));
const PdfInkModuleClient::DocumentV2InkPathShapesMap result =
plugin_->ink_module_client_for_testing()->LoadV2InkPathsFromPdf();
EXPECT_THAT(
result,
ElementsAre(Pair(0, ElementsAre(Pair(InkModeledShapeId(0), _))),
Pair(1, ElementsAre(Pair(InkModeledShapeId(1), _),
Pair(InkModeledShapeId(2), _))),
Pair(12, ElementsAre(Pair(InkModeledShapeId(3), _),
Pair(InkModeledShapeId(4), _),
Pair(InkModeledShapeId(5), _)))));
EXPECT_CALL(*engine_ptr_, UpdateShapeActive(0, InkModeledShapeId(0), false));
plugin_->ink_module_client_for_testing()->UpdateShapeActive(
/*page_index=*/0, InkModeledShapeId(0),
/*active=*/false);
}
TEST_F(PdfViewWebPluginInkTest, SendThumbnailUpdatesInkThumbnail) {
SetUpWithTrivialInkStrokes();
EXPECT_CALL(*client_ptr_, PostMessage)
.WillOnce([](const base::Value::Dict& dict) {
auto expected = base::test::ParseJsonDict(R"({
"type": "getThumbnailReply",
"messageId": "foo",
"width": 216,
"height": 108,
})");
EXPECT_THAT(dict, base::test::DictionaryHasValues(expected));
// Test `dict` contains the image data, but not the exact value.
const auto* blob = dict.FindBlob("imageData");
ASSERT_TRUE(blob);
EXPECT_FALSE(blob->empty());
})
.WillOnce([](const base::Value::Dict& dict) {
auto expected = base::test::ParseJsonDict(R"({
"type": "updateInk2Thumbnail",
"pageNumber": 1,
"width": 216,
"height": 108,
})");
EXPECT_THAT(dict, base::test::DictionaryHasValues(expected));
// Test `dict` contains the image data, but not the exact value.
const auto* blob = dict.FindBlob("imageData");
ASSERT_TRUE(blob);
EXPECT_FALSE(blob->empty());
});
SendThumbnail(/*message_id=*/"foo", /*page_size=*/{50, 25});
}
TEST_F(PdfViewWebPluginInkTest, SendThumbnailWithNoStrokes) {
EXPECT_CALL(*client_ptr_, PostMessage)
.WillOnce([](const base::Value::Dict& dict) {
auto expected = base::test::ParseJsonDict(R"({
"type": "getThumbnailReply",
"messageId": "foo",
"width": 216,
"height": 108,
})");
EXPECT_THAT(dict, base::test::DictionaryHasValues(expected));
// Test `dict` contains the image data, but not the exact value.
const auto* blob = dict.FindBlob("imageData");
ASSERT_TRUE(blob);
EXPECT_FALSE(blob->empty());
});
SendThumbnail(/*message_id=*/"foo", /*page_size=*/{50, 25});
}
TEST_F(PdfViewWebPluginInkTest, UpdateCursor) {
UpdatePluginGeometryWithoutWaiting(2.0f, {0, 0, 20, 20});
ON_CALL(*engine_ptr_, HandleInputEvent)
.WillByDefault([this](const blink::WebInputEvent& event) -> bool {
plugin_->UpdateCursor(ui::mojom::CursorType::kPointer);
return false;
});
blink::WebMouseEvent mouse_event;
mouse_event.SetType(blink::WebInputEvent::Type::kMouseMove);
mouse_event.SetPositionInWidget(10.0f, 20.0f);
ui::Cursor cursor;
EXPECT_EQ(ui::mojom::CursorType::kNull, cursor.type());
cursor =
TestSendInputEvent(mouse_event, blink::WebInputEventResult::kNotHandled);
EXPECT_EQ(ui::mojom::CursorType::kPointer, cursor.type());
plugin_->OnMessage(CreateSetAnnotationModeMessageForTesting(/*enable=*/true));
cursor =
TestSendInputEvent(mouse_event, blink::WebInputEventResult::kNotHandled);
EXPECT_EQ(ui::mojom::CursorType::kCustom, cursor.type());
plugin_->OnMessage(
CreateSetAnnotationModeMessageForTesting(/*enable=*/false));
cursor =
TestSendInputEvent(mouse_event, blink::WebInputEventResult::kNotHandled);
EXPECT_EQ(ui::mojom::CursorType::kPointer, cursor.type());
}
TEST_F(PdfViewWebPluginInkTest, GetPageSizeInPoints) {
SetUpWithTrivialInkStrokes();
EXPECT_EQ(gfx::SizeF(75.0f, 37.5f),
plugin_->ink_module_client_for_testing()->GetPageSizeInPoints(
/*page_index=*/0));
}
TEST_F(PdfViewWebPluginInkTest, GetThumbnailSize) {
SetUpWithTrivialInkStrokes();
EXPECT_EQ(gfx::Size(50, 25),
plugin_->ink_module_client_for_testing()->GetThumbnailSize(
/*page_index=*/0));
}
TEST_F(PdfViewWebPluginInkTest, GetZoom) {
// Demonstrate that default zoom is identity.
EXPECT_EQ(1.0f, plugin_->ink_module_client_for_testing()->GetZoom());
// Verify that changing the plugin zoom shows effect.
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
plugin_->OnMessage(ParseMessage(R"({
"type": "viewport",
"userInitiated": false,
"zoom": 2,
"layoutOptions": {
"direction": 0,
"defaultPageOrientation": 0,
"twoUpViewEnabled": false,
},
"xOffset": 0,
"yOffset": 0,
"pinchPhase": 0,
})"));
EXPECT_EQ(2.0f, plugin_->ink_module_client_for_testing()->GetZoom());
// Verify that changing the platform device scale shows effect.
ON_CALL(*client_ptr_, DeviceScaleFactor).WillByDefault(Return(1.25f));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.5f));
constexpr gfx::Rect kWindowRect(12, 24, 36, 48);
plugin_->UpdateGeometry(kWindowRect, kWindowRect, kWindowRect,
/*is_visible=*/true);
EXPECT_EQ(2.5f, plugin_->ink_module_client_for_testing()->GetZoom());
}
TEST_F(PdfViewWebPluginInkTest, RequestThumbnail) {
EXPECT_CALL(*engine_ptr_, RequestThumbnail)
.WillOnce([](int page_index, float device_pixel_ratio,
SendThumbnailCallback send_callback) {
EXPECT_EQ(0, page_index);
EXPECT_EQ(1.0f, device_pixel_ratio);
std::move(send_callback)
.Run(Thumbnail(gfx::SizeF(612, 792), device_pixel_ratio));
});
base::test::TestFuture<Thumbnail> future;
plugin_->ink_module_client_for_testing()->RequestThumbnail(
/*page_index=*/0, future.GetCallback());
ASSERT_TRUE(future.Wait());
Thumbnail thumbnail = future.Take();
EXPECT_EQ(gfx::Size(108, 140), thumbnail.image_size());
EXPECT_EQ(1.0f, thumbnail.device_pixel_ratio());
}
TEST_F(PdfViewWebPluginInkTest, AddUpdateDiscardStroke) {
const PdfInkBrush kBrush(PdfInkBrush::Type::kPen, SK_ColorRED, /*size=*/4.0f);
constexpr InkStrokeId kStrokeId(1);
constexpr int kPageIndex = 0;
EXPECT_CALL(*engine_ptr_, ApplyStroke(kPageIndex, kStrokeId, _));
const ink::Stroke kStroke(kBrush.ink_brush());
plugin_->ink_module_client_for_testing()->StrokeAdded(kPageIndex, kStrokeId,
kStroke);
EXPECT_CALL(*engine_ptr_, UpdateStrokeActive(kPageIndex, kStrokeId, false));
plugin_->ink_module_client_for_testing()->UpdateStrokeActive(
kPageIndex, kStrokeId,
/*active=*/false);
EXPECT_CALL(*engine_ptr_, DiscardStroke(kPageIndex, kStrokeId));
plugin_->ink_module_client_for_testing()->DiscardStroke(kPageIndex,
kStrokeId);
}
TEST_F(PdfViewWebPluginInkTest, VisiblePageIndexFromPoint) {
ON_CALL(*engine_ptr_, GetPageContentsRect)
.WillByDefault([](int page_index) -> gfx::Rect {
// Uniform 80x180 page sizes, with a `kVerticalEmptySpace` gap above
// every page.
constexpr int kVerticalEmptySpace = 20;
constexpr int kHeight = 180;
int y = kHeight * page_index + kVerticalEmptySpace * (page_index + 1);
return gfx::Rect(/*x=*/10, /*y=*/y, /*width=*/80, /*height=*/kHeight);
});
// Top-left corner of screen.
constexpr gfx::PointF kScreenTopLeftCorner(0.0f, 0.0f);
// Top-left corner of first page.
constexpr gfx::PointF kPage0TopLeftCorner(10.0f, 20.0f);
// Just outside the top-left corner of first page.
constexpr gfx::PointF kPage0OutsideTopLeftCorner(10.0f, 19.938f);
// Bottom-right corner of first page.
constexpr gfx::PointF kPage0BottomRightCorner(89.999f, 199.0f);
// Just outside the bottom-right corner of first page.
constexpr gfx::PointF kPage0OutsideBottomRightCorner(90.0f, 199.0f);
// Gap between first and second page.
constexpr gfx::PointF kPage0Page1Gap(50.0f, 201.0f);
// Top of second page.
constexpr gfx::PointF kPage1Top(50.0f, 220.0f);
// Middle of last page.
constexpr gfx::PointF kPage12Middle(50.0f, 2510.0f);
// Bottom of last page.
constexpr gfx::PointF kPage12Bottom(60.0f, 2599.0f);
// Beyond the last page.
constexpr gfx::PointF kPageBelowLast(60.0f, 2700.0f);
// Start with the first 2 pages visible.
ON_CALL(*engine_ptr_, IsPageVisible)
.WillByDefault([](int page_index) -> bool {
return page_index >= 0 && page_index <= 1;
});
PdfInkModuleClient* ink_module_client =
plugin_->ink_module_client_for_testing();
EXPECT_EQ(-1,
ink_module_client->VisiblePageIndexFromPoint(kScreenTopLeftCorner));
EXPECT_EQ(0,
ink_module_client->VisiblePageIndexFromPoint(kPage0TopLeftCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(
kPage0OutsideTopLeftCorner));
EXPECT_EQ(
0, ink_module_client->VisiblePageIndexFromPoint(kPage0BottomRightCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(
kPage0OutsideBottomRightCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPage0Page1Gap));
EXPECT_EQ(1, ink_module_client->VisiblePageIndexFromPoint(kPage1Top));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPage12Middle));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPage12Bottom));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPageBelowLast));
// Change the visible page to the last page.
ON_CALL(*engine_ptr_, IsPageVisible)
.WillByDefault([](int page_index) -> bool { return page_index == 12; });
EXPECT_EQ(-1,
ink_module_client->VisiblePageIndexFromPoint(kScreenTopLeftCorner));
EXPECT_EQ(-1,
ink_module_client->VisiblePageIndexFromPoint(kPage0TopLeftCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(
kPage0OutsideTopLeftCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(
kPage0BottomRightCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(
kPage0OutsideBottomRightCorner));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPage0Page1Gap));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPage1Top));
EXPECT_EQ(12, ink_module_client->VisiblePageIndexFromPoint(kPage12Middle));
EXPECT_EQ(12, ink_module_client->VisiblePageIndexFromPoint(kPage12Bottom));
EXPECT_EQ(-1, ink_module_client->VisiblePageIndexFromPoint(kPageBelowLast));
}
TEST_F(PdfViewWebPluginInkTest, AnnotationModeSetsFormAndClearsText) {
EXPECT_CALL(*engine_ptr_, SetFormHighlight(false));
EXPECT_CALL(*engine_ptr_, ClearTextSelection());
plugin_->OnMessage(CreateSetAnnotationModeMessageForTesting(/*enable=*/true));
EXPECT_TRUE(plugin_->IsInAnnotationMode());
EXPECT_CALL(*engine_ptr_, SetFormHighlight(true));
plugin_->OnMessage(
CreateSetAnnotationModeMessageForTesting(/*enable=*/false));
EXPECT_FALSE(plugin_->IsInAnnotationMode());
}
TEST_F(PdfViewWebPluginInkTest, DrawInProgressStroke) {
plugin_->set_in_paint_for_testing(true);
constexpr gfx::Rect kScreenRect(kCanvasSize);
constexpr gfx::SizeF kPageSizeInPoints(
kCanvasSize.width() * kUnitConversionFactorPixelsToPoints,
kCanvasSize.height() * kUnitConversionFactorPixelsToPoints);
ON_CALL(*engine_ptr_, GetPageContentsRect).WillByDefault(Return(kScreenRect));
ON_CALL(*engine_ptr_, GetPageSizeInPoints)
.WillByDefault(Return(kPageSizeInPoints));
ON_CALL(*engine_ptr_, GetThumbnailSize)
.WillByDefault(Return(gfx::Size(50, 50)));
ON_CALL(*engine_ptr_, IsPageVisible).WillByDefault(Return(true));
UpdatePluginGeometry(/*device_scale=*/1.0f, kScreenRect);
// The canvas starts blank.
canvas_.DrawColor(SK_ColorWHITE);
plugin_->Paint(canvas_.sk_canvas(), kScreenRect);
SkBitmap blank_bitmap =
GenerateExpectedBitmapForPaint(kScreenRect, SK_ColorWHITE);
EXPECT_TRUE(cc::MatchesBitmap(canvas_.GetBitmap(), blank_bitmap,
cc::ExactPixelComparator()));
// Start to draw a stroke. There should not be a call to apply the stroke
// until drawing is finished.
EXPECT_CALL(*engine_ptr_, ApplyStroke(_, _, _)).Times(0);
plugin_->OnMessage(CreateSetAnnotationModeMessageForTesting(/*enable=*/true));
// The final imaging for a stroke saved to a PDF should match what was final
// drawn result when it was in-progress.
TestSendInputEvent(
MouseEventBuilder().CreateLeftClickAtPosition({95, 85}).Build(),
blink::WebInputEventResult::kHandledApplication);
static constexpr gfx::PointF kStrokeEndingPosition(50, 45);
TestSendInputEvent(MouseEventBuilder()
.SetType(blink::WebInputEvent::Type::kMouseMove)
.SetPosition(kStrokeEndingPosition)
.SetButton(blink::WebPointerProperties::Button::kLeft)
.Build(),
blink::WebInputEventResult::kHandledApplication);
// Draw the canvas for the in-progress stroke.
plugin_->Paint(canvas_.sk_canvas(), kScreenRect);
EXPECT_TRUE(MatchesPngFile(
canvas_.GetBitmap().asImage().get(),
GetInkTestDataFilePath(FILE_PATH_LITERAL("diagonal_stroke.png"))));
// Finish the stroke. After a stroke is finished there is nothing more to
// be drawn by PdfInkModule, as the completed stroke is provided by a
// callback to be applied to a PDF page.
testing::Mock::VerifyAndClearExpectations(engine_ptr_);
EXPECT_CALL(*engine_ptr_, ApplyStroke(/*page_index=*/0, InkStrokeId(0), _));
TestSendInputEvent(MouseEventBuilder()
.CreateLeftMouseUpAtPosition(kStrokeEndingPosition)
.Build(),
blink::WebInputEventResult::kHandledApplication);
plugin_->Paint(canvas_.sk_canvas(), kScreenRect);
EXPECT_TRUE(cc::MatchesBitmap(canvas_.GetBitmap(), blank_bitmap,
cc::ExactPixelComparator()));
}
class PdfViewWebPluginInk2SaveTest : public PdfViewWebPluginSaveTest {
private:
base::test::ScopedFeatureList feature_list_{features::kPdfInk2};
};
TEST_F(PdfViewWebPluginInk2SaveTest, AnnotationInNonEditMode) {
plugin_->ink_module_client_for_testing()->StrokeFinished();
base::Value expected_response = base::test::ParseJson(R"({
"type": "saveData",
"token": "annotation-in-non-edit-mode",
"fileName": "example.pdf",
"editModeForTesting": false,
})");
AddDataToValue(base::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": 0,
"token": "annotation-in-non-edit-mode",
})"));
pdf_receiver_.FlushForTesting();
}
TEST_F(PdfViewWebPluginInk2SaveTest, AnnotationInEditMode) {
plugin_->ink_module_client_for_testing()->StrokeFinished();
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::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": 0,
"token": "annotation-in-edit-mode",
})"));
pdf_receiver_.FlushForTesting();
}
using PdfViewWebPluginInkMetricTest = PdfViewWebPluginInkTest;
TEST_F(PdfViewWebPluginInkMetricTest, LoadedWithoutV2InkAnnotations) {
base::HistogramTester histograms;
EXPECT_CALL(*engine_ptr_, ContainsV2InkPath(_))
.WillOnce(Return(PDFLoadedWithV2InkAnnotations::kFalse));
plugin_->DocumentLoadComplete();
histograms.ExpectUniqueSample(kPdfLoadedWithV2InkAnnotationsMetric,
PDFLoadedWithV2InkAnnotations::kFalse, 1);
}
TEST_F(PdfViewWebPluginInkMetricTest, LoadedWithV2InkAnnotations) {
base::HistogramTester histograms;
EXPECT_CALL(*engine_ptr_, ContainsV2InkPath(_))
.WillOnce(Return(PDFLoadedWithV2InkAnnotations::kTrue));
plugin_->DocumentLoadComplete();
histograms.ExpectUniqueSample(kPdfLoadedWithV2InkAnnotationsMetric,
PDFLoadedWithV2InkAnnotations::kTrue, 1);
}
TEST_F(PdfViewWebPluginInkMetricTest, LoadedWithV2InkAnnotationsTimeout) {
base::HistogramTester histograms;
EXPECT_CALL(*engine_ptr_, ContainsV2InkPath(_))
.WillOnce(Return(PDFLoadedWithV2InkAnnotations::kUnknown));
plugin_->DocumentLoadComplete();
histograms.ExpectUniqueSample(kPdfLoadedWithV2InkAnnotationsMetric,
PDFLoadedWithV2InkAnnotations::kUnknown, 1);
}
class PdfViewWebPluginPrintPreviewInkMetricTest
: public PdfViewWebPluginPrintPreviewTest {
private:
base::test::ScopedFeatureList feature_list_{features::kPdfInk2};
};
TEST_F(PdfViewWebPluginPrintPreviewInkMetricTest,
LoadedWithV2InkAnnotationsDoesNotCountPrintPreview) {
base::HistogramTester histograms;
OnMessageWithEngineUpdate(ParseMessage(R"({
"type": "resetPrintPreviewMode",
"url": "chrome-untrusted://print/0/0/print.pdf",
"grayscale": false,
"pageCount": 1,
})"));
EXPECT_CALL(*engine_ptr_, ContainsV2InkPath(_)).Times(0);
plugin_->DocumentLoadComplete();
// The V2 ink annotations PDF load metric should not increment for Print
// Preview.
histograms.ExpectTotalCount(kPdfLoadedWithV2InkAnnotationsMetric, 0);
}
#endif // BUILDFLAG(ENABLE_PDF_INK2)
} // namespace chrome_pdf