blob: 0d39071a88ab5cb2f71231dfda1a0e706d3e005e [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "pdf/pdf_view_web_plugin.h"
#include <memory>
#include <string>
#include <utility>
#include "base/strings/string_piece.h"
#include "cc/paint/paint_canvas.h"
#include "cc/test/pixel_comparator.h"
#include "cc/test/pixel_test_utils.h"
#include "pdf/ppapi_migration/bitmap.h"
#include "pdf/test/test_helpers.h"
#include "pdf/test/test_pdfium_engine.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_keyboard_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/common/input/web_pointer_properties.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_text_input_type.h"
#include "third_party/blink/public/platform/web_vector.h"
#include "third_party/blink/public/web/web_associated_url_loader.h"
#include "third_party/blink/public/web/web_plugin_container.h"
#include "third_party/blink/public/web/web_plugin_params.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/cursor/cursor.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/latency/latency_info.h"
namespace chrome_pdf {
namespace {
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Pointwise;
using ::testing::Return;
// `kCanvasSize` needs to be big enough to hold plugin's snapshots during
// testing.
constexpr gfx::Size kCanvasSize(100, 100);
// A common device scale for high DPI displays.
constexpr float kDeviceScale = 2.0f;
// Note: Make sure `kDefaultColor` is different from `kPaintColor` and the
// plugin's background color. This will help identify bitmap changes after
// painting.
constexpr SkColor kDefaultColor = SK_ColorGREEN;
constexpr SkColor kPaintColor = SK_ColorRED;
struct PaintParams {
// The plugin container's device scale.
float device_scale;
// The window area in CSS pixels.
gfx::Rect window_rect;
// The target painting area on the canvas in CSS pixels.
gfx::Rect paint_rect;
// The expected clipped area to be filled with paint color. The clipped area
// should be the intersection of `paint_rect` and `window_rect`.
gfx::Rect expected_clipped_rect;
};
MATCHER(SearchStringResultEq, "") {
PDFEngine::Client::SearchStringResult l = std::get<0>(arg);
PDFEngine::Client::SearchStringResult r = std::get<1>(arg);
return l.start_index == r.start_index && l.length == r.length;
}
MATCHER_P(IsExpectedImeKeyEvent, expected_text, "") {
if (arg.GetType() != blink::WebInputEvent::Type::kChar)
return false;
const auto& event = static_cast<const blink::WebKeyboardEvent&>(arg);
return event.GetModifiers() == blink::WebInputEvent::kNoModifiers &&
event.windows_key_code == expected_text[0] &&
event.native_key_code == expected_text[0] &&
event.dom_code == static_cast<int>(ui::DomCode::NONE) &&
event.dom_key == ui::DomKey::NONE && !event.is_system_key &&
!event.is_browser_shortcut && event.text == expected_text &&
event.unmodified_text == expected_text;
}
// Generates the expected `SkBitmap` with `paint_color` filled in the expected
// clipped area and `kDefaultColor` as the background color.
SkBitmap GenerateExpectedBitmapForPaint(const gfx::Rect& expected_clipped_rect,
SkColor paint_color) {
SkBitmap expected_bitmap =
CreateN32PremulSkBitmap(gfx::SizeToSkISize(kCanvasSize));
expected_bitmap.eraseColor(kDefaultColor);
expected_bitmap.erase(paint_color, gfx::RectToSkIRect(expected_clipped_rect));
return expected_bitmap;
}
blink::WebMouseEvent CreateDefaultMouseDownEvent() {
blink::WebMouseEvent web_event(
blink::WebInputEvent::Type::kMouseDown,
/*position=*/gfx::PointF(),
/*global_position=*/gfx::PointF(),
blink::WebPointerProperties::Button::kLeft,
/*click_count_param=*/1, blink::WebInputEvent::Modifiers::kLeftButtonDown,
blink::WebInputEvent::GetStaticTimeStampForTests());
web_event.SetFrameScale(1);
return web_event;
}
class FakeContainerWrapper : public PdfViewWebPlugin::ContainerWrapper {
public:
explicit FakeContainerWrapper(PdfViewWebPlugin* web_plugin)
: web_plugin_(web_plugin) {
ON_CALL(*this, UpdateTextInputState)
.WillByDefault(Invoke(
this, &FakeContainerWrapper::UpdateTextInputStateFromPlugin));
UpdateTextInputStateFromPlugin();
}
FakeContainerWrapper(const FakeContainerWrapper&) = delete;
FakeContainerWrapper& operator=(const FakeContainerWrapper&) = delete;
~FakeContainerWrapper() override = default;
// PdfViewWebPlugin::ContainerWrapper:
void Invalidate() override {}
MOCK_METHOD(void,
RequestTouchEventType,
(blink::WebPluginContainer::TouchEventRequestType),
(override));
MOCK_METHOD(void, ReportFindInPageMatchCount, (int, int, bool), (override));
MOCK_METHOD(void, ReportFindInPageSelection, (int, int), (override));
float DeviceScaleFactor() override { return device_scale_; }
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));
std::string GetEmbedderOriginString() override {
return "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/";
}
blink::WebLocalFrame* GetFrame() override { return nullptr; }
blink::WebLocalFrameClient* GetWebLocalFrameClient() override {
return nullptr;
}
// TODO(https://crbug.com/1207575): Container() should not be used for testing
// since it doesn't have a valid blink::WebPluginContainer. Make this method
// fail once ContainerWrapper instead of blink::WebPluginContainer is used for
// initializing `PostMessageSender`.
blink::WebPluginContainer* Container() override { return nullptr; }
blink::WebTextInputType widget_text_input_type() const {
return widget_text_input_type_;
}
void set_device_scale(float device_scale) { device_scale_ = device_scale; }
private:
void UpdateTextInputStateFromPlugin() {
widget_text_input_type_ = web_plugin_->GetPluginTextInputType();
}
float device_scale_ = 1.0f;
// Represents the frame widget's text input type.
blink::WebTextInputType widget_text_input_type_;
PdfViewWebPlugin* web_plugin_;
};
class FakePdfViewWebPluginClient : public PdfViewWebPlugin::Client {
public:
FakePdfViewWebPluginClient() = default;
FakePdfViewWebPluginClient(const FakePdfViewWebPluginClient&) = delete;
FakePdfViewWebPluginClient& operator=(const FakePdfViewWebPluginClient&) =
delete;
~FakePdfViewWebPluginClient() override = default;
// PdfViewWebPlugin::Client:
MOCK_METHOD(bool, IsUseZoomForDSFEnabled, (), (const, override));
};
} // namespace
class PdfViewWebPluginWithoutInitializeTest : public testing::Test {
public:
PdfViewWebPluginWithoutInitializeTest(
const PdfViewWebPluginWithoutInitializeTest&) = delete;
PdfViewWebPluginWithoutInitializeTest& operator=(
const PdfViewWebPluginWithoutInitializeTest&) = delete;
protected:
// Custom deleter for `plugin_`. PdfViewWebPlugin must be destroyed by
// PdfViewWebPlugin::Destroy() instead of its destructor.
struct PluginDeleter {
void operator()(PdfViewWebPlugin* ptr) { ptr->Destroy(); }
};
PdfViewWebPluginWithoutInitializeTest() = default;
~PdfViewWebPluginWithoutInitializeTest() override = default;
void SetUp() override {
// Set a dummy URL for initializing the plugin.
blink::WebPluginParams params;
params.attribute_names.push_back(blink::WebString("src"));
params.attribute_values.push_back(blink::WebString("dummy.pdf"));
auto client = std::make_unique<NiceMock<FakePdfViewWebPluginClient>>();
client_ptr_ = client.get();
mojo::AssociatedRemote<pdf::mojom::PdfService> unbound_remote;
plugin_ =
std::unique_ptr<PdfViewWebPlugin, PluginDeleter>(new PdfViewWebPlugin(
std::move(client), std::move(unbound_remote), params));
}
void TearDown() override { plugin_.reset(); }
FakePdfViewWebPluginClient* client_ptr_;
std::unique_ptr<PdfViewWebPlugin, PluginDeleter> plugin_;
};
class PdfViewWebPluginTest : public PdfViewWebPluginWithoutInitializeTest {
protected:
void SetUp() override {
PdfViewWebPluginWithoutInitializeTest::SetUp();
auto wrapper =
std::make_unique<NiceMock<FakeContainerWrapper>>(plugin_.get());
wrapper_ptr_ = wrapper.get();
auto engine = CreateEngine();
engine_ptr_ = engine.get();
EXPECT_TRUE(
plugin_->InitializeForTesting(std::move(wrapper), std::move(engine)));
}
void TearDown() override {
wrapper_ptr_ = nullptr;
PdfViewWebPluginWithoutInitializeTest::TearDown();
}
// Allow derived test classes to create their own custom TestPDFiumEngine.
virtual std::unique_ptr<TestPDFiumEngine> CreateEngine() {
return std::make_unique<TestPDFiumEngine>(plugin_.get());
}
void UpdatePluginGeometry(float device_scale, const gfx::Rect& window_rect) {
// The plugin container's device scale must be set before calling
// UpdateGeometry().
ASSERT_TRUE(wrapper_ptr_);
wrapper_ptr_->set_device_scale(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) {
UpdatePluginGeometry(device_scale, window_rect);
EXPECT_EQ(expected_device_scale, plugin_->GetDeviceScaleForTesting())
<< "Device scale comparison failure at device scale of "
<< device_scale;
EXPECT_EQ(expected_plugin_rect, plugin_->GetPluginRectForTesting())
<< "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) {
UpdatePluginGeometry(device_scale, window_rect);
canvas_.DrawColor(kDefaultColor);
plugin_->Paint(canvas_.sk_canvas(), paint_rect);
// Expect the clipped area on canvas to be filled with plugin's background
// color.
SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint(
expected_clipped_rect, plugin_->GetBackgroundColor());
EXPECT_TRUE(
cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap,
cc::ExactPixelComparator(/*discard_alpha=*/false)))
<< "Failure at device scale of " << device_scale << ", window rect of "
<< window_rect.ToString();
}
void TestPaintSnapshots(float device_scale,
const gfx::Rect& window_rect,
const gfx::Rect& paint_rect,
const gfx::Rect& expected_clipped_rect) {
UpdatePluginGeometry(device_scale, window_rect);
canvas_.DrawColor(kDefaultColor);
// Fill the graphics device with `kPaintColor` and update the plugin's
// snapshot.
const gfx::Rect& plugin_rect = plugin_->GetPluginRectForTesting();
std::unique_ptr<Graphics> graphics =
plugin_->CreatePaintGraphics(plugin_rect.size());
graphics->PaintImage(
CreateSkiaImageForTesting(plugin_rect.size(), kPaintColor),
gfx::Rect(plugin_rect.width(), plugin_rect.height()));
graphics->Flush(base::DoNothing());
plugin_->Paint(canvas_.sk_canvas(), paint_rect);
// Expect the clipped area on canvas to be filled with `kPaintColor`.
SkBitmap expected_bitmap =
GenerateExpectedBitmapForPaint(expected_clipped_rect, kPaintColor);
EXPECT_TRUE(
cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap,
cc::ExactPixelComparator(/*discard_alpha=*/false)))
<< "Failure at device scale of " << device_scale << ", window rect of "
<< window_rect.ToString();
}
TestPDFiumEngine* engine_ptr_;
FakeContainerWrapper* wrapper_ptr_;
// Provides the cc::PaintCanvas for painting.
gfx::Canvas canvas_{kCanvasSize, /*image_scale=*/1.0f, /*is_opaque=*/true};
};
TEST_F(PdfViewWebPluginWithoutInitializeTest, Initialize) {
auto wrapper =
std::make_unique<NiceMock<FakeContainerWrapper>>(plugin_.get());
auto engine = std::make_unique<TestPDFiumEngine>(plugin_.get());
EXPECT_CALL(*wrapper,
RequestTouchEventType(
blink::WebPluginContainer::kTouchEventRequestTypeRaw));
EXPECT_TRUE(
plugin_->InitializeForTesting(std::move(wrapper), std::move(engine)));
}
TEST_F(PdfViewWebPluginTest, UpdateGeometrySetsPluginRectUseZoomForDSFEnabled) {
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(true));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
TestUpdateGeometrySetsPluginRect(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(4, 4, 12, 12),
/*expected_device_scale=*/2.0f,
/*expected_plugin_rect=*/gfx::Rect(4, 4, 12, 12));
}
TEST_F(PdfViewWebPluginTest,
UpdateGeometrySetsPluginRectUseZoomForDSFDisabled) {
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(false));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
TestUpdateGeometrySetsPluginRect(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(4, 4, 12, 12),
/*expected_device_scale=*/2.0f,
/*expected_plugin_rect=*/gfx::Rect(8, 8, 24, 24));
}
TEST_F(PdfViewWebPluginTest,
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;
};
// Keep the using zoom for DSF setting consistent within the test.
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(true));
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);
}
}
class PdfViewWebPluginTestUseZoomForDSF
: public PdfViewWebPluginTest,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
PdfViewWebPluginTest::SetUp();
ON_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillByDefault(Return(GetParam()));
}
};
TEST_P(PdfViewWebPluginTestUseZoomForDSF,
UpdateGeometrySetsPluginRectWithEmptyWindow) {
EXPECT_CALL(*engine_ptr_, ZoomUpdated).Times(0);
TestUpdateGeometrySetsPluginRect(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(2, 2, 0, 0),
/*expected_device_scale=*/1.0f, /*expected_plugin_rect=*/gfx::Rect());
}
TEST_P(PdfViewWebPluginTestUseZoomForDSF, PaintEmptySnapshots) {
TestPaintEmptySnapshots(/*device_scale=*/4.0f,
/*window_rect=*/gfx::Rect(10, 10, 20, 20),
/*paint_rect=*/gfx::Rect(5, 5, 15, 15),
/*expected_clipped_rect=*/gfx::Rect(10, 10, 10, 10));
}
TEST_P(PdfViewWebPluginTestUseZoomForDSF, PaintSnapshots) {
TestPaintSnapshots(/*device_scale=*/4.0f,
/*window_rect=*/gfx::Rect(10, 10, 20, 20),
/*paint_rect=*/gfx::Rect(5, 5, 15, 15),
/*expected_clipped_rect=*/gfx::Rect(10, 10, 10, 10));
}
TEST_P(PdfViewWebPluginTestUseZoomForDSF,
PaintSnapshotsWithVariousDeviceScales) {
static constexpr PaintParams kPaintWithVariousScalesParams[] = {
{0.4f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30),
gfx::Rect(10, 10, 28, 28)},
{1.0f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30),
gfx::Rect(10, 10, 28, 28)},
{4.0f, gfx::Rect(8, 8, 30, 30), gfx::Rect(10, 10, 30, 30),
gfx::Rect(10, 10, 28, 28)},
};
for (const auto& params : kPaintWithVariousScalesParams) {
TestPaintSnapshots(params.device_scale, params.window_rect,
params.paint_rect, params.expected_clipped_rect);
}
}
TEST_P(PdfViewWebPluginTestUseZoomForDSF,
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);
}
}
INSTANTIATE_TEST_SUITE_P(All,
PdfViewWebPluginTestUseZoomForDSF,
testing::Bool());
class PdfViewWebPluginMouseEventsTest : public PdfViewWebPluginTest {
public:
class TestPDFiumEngineForMouseEvents : public TestPDFiumEngine {
public:
explicit TestPDFiumEngineForMouseEvents(PDFEngine::Client* client)
: TestPDFiumEngine(client) {}
// TestPDFiumEngine:
bool HandleInputEvent(const blink::WebInputEvent& event) override {
// Since blink::WebInputEvent is an abstract class, we cannot use equal
// matcher to verify its value. Here we test with blink::WebMouseEvent
// specifically.
if (!blink::WebInputEvent::IsMouseEventType(event.GetType()))
return false;
scaled_mouse_event_ = std::make_unique<blink::WebMouseEvent>();
*scaled_mouse_event_ = static_cast<const blink::WebMouseEvent&>(event);
return true;
}
const blink::WebMouseEvent* GetScaledMouseEvent() const {
return scaled_mouse_event_.get();
}
private:
std::unique_ptr<blink::WebMouseEvent> scaled_mouse_event_;
};
std::unique_ptr<TestPDFiumEngine> CreateEngine() override {
return std::make_unique<TestPDFiumEngineForMouseEvents>(plugin_.get());
}
TestPDFiumEngineForMouseEvents* engine() {
return static_cast<TestPDFiumEngineForMouseEvents*>(engine_ptr_);
}
};
TEST_F(PdfViewWebPluginMouseEventsTest,
HandleInputEventWithUseZoomForDSFEnabled) {
// Test when using zoom for DSF is enabled.
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(true));
wrapper_ptr_->set_device_scale(kDeviceScale);
UpdatePluginGeometry(kDeviceScale, gfx::Rect(20, 20));
ui::Cursor dummy_cursor;
plugin_->HandleInputEvent(
blink::WebCoalescedInputEvent(CreateDefaultMouseDownEvent(),
ui::LatencyInfo()),
&dummy_cursor);
const blink::WebMouseEvent* event = engine()->GetScaledMouseEvent();
ASSERT_TRUE(event);
EXPECT_EQ(gfx::PointF(-10.0f, 0.0f), event->PositionInWidget());
}
TEST_F(PdfViewWebPluginMouseEventsTest,
HandleInputEventWithUseZoomForDSFDisabled) {
// Test when using zoom for DSF is disabled.
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(false));
wrapper_ptr_->set_device_scale(kDeviceScale);
UpdatePluginGeometry(kDeviceScale, gfx::Rect(20, 20));
ui::Cursor dummy_cursor;
plugin_->HandleInputEvent(
blink::WebCoalescedInputEvent(CreateDefaultMouseDownEvent(),
ui::LatencyInfo()),
&dummy_cursor);
const blink::WebMouseEvent* event = engine()->GetScaledMouseEvent();
ASSERT_TRUE(event);
EXPECT_EQ(gfx::PointF(-20.0f, 0.0f), event->PositionInWidget());
}
class PdfViewWebPluginImeTest : public PdfViewWebPluginTest {
public:
class TestPDFiumEngineForIme : public TestPDFiumEngine {
public:
explicit TestPDFiumEngineForIme(PDFEngine::Client* client)
: TestPDFiumEngine(client) {}
// TestPDFiumEngine:
MOCK_METHOD(bool,
HandleInputEvent,
(const blink::WebInputEvent&),
(override));
};
std::unique_ptr<TestPDFiumEngine> CreateEngine() override {
return std::make_unique<TestPDFiumEngineForIme>(plugin_.get());
}
TestPDFiumEngineForIme* engine() {
return static_cast<TestPDFiumEngineForIme*>(engine_ptr_);
}
void TestImeSetCompositionForPlugin(const blink::WebString& text) {
EXPECT_CALL(*engine(), HandleInputEvent).Times(0);
plugin_->ImeSetCompositionForPlugin(text, std::vector<ui::ImeTextSpan>(),
gfx::Range(),
/*selection_start=*/0,
/*selection_end=*/0);
}
void TestImeFinishComposingTextForPlugin(
const blink::WebString& expected_text) {
InSequence sequence;
std::u16string expected_text16 = expected_text.Utf16();
if (expected_text16.size()) {
for (const auto& c : expected_text16) {
base::StringPiece16 expected_key(&c, 1);
EXPECT_CALL(*engine(),
HandleInputEvent(IsExpectedImeKeyEvent(expected_key)))
.WillOnce(Return(true));
}
} else {
EXPECT_CALL(*engine(), HandleInputEvent).Times(0);
}
plugin_->ImeFinishComposingTextForPlugin(false);
}
void TestImeCommitTextForPlugin(const blink::WebString& text) {
InSequence sequence;
std::u16string expected_text16 = text.Utf16();
if (expected_text16.size()) {
for (const auto& c : expected_text16) {
base::StringPiece16 event(&c, 1);
EXPECT_CALL(*engine(), HandleInputEvent(IsExpectedImeKeyEvent(event)))
.WillOnce(Return(true));
}
} else {
EXPECT_CALL(*engine(), HandleInputEvent).Times(0);
}
plugin_->ImeCommitTextForPlugin(text, std::vector<ui::ImeTextSpan>(),
gfx::Range(),
/*relative_cursor_pos=*/0);
}
};
TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishAscii) {
const blink::WebString text = blink::WebString::FromASCII("input");
TestImeSetCompositionForPlugin(text);
TestImeFinishComposingTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishUnicode) {
const blink::WebString text = blink::WebString::FromUTF16(u"你好");
TestImeSetCompositionForPlugin(text);
TestImeFinishComposingTextForPlugin(text);
// Calling ImeFinishComposingTextForPlugin() again is a no-op.
TestImeFinishComposingTextForPlugin("");
}
TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishEmpty) {
const blink::WebString text;
TestImeSetCompositionForPlugin(text);
TestImeFinishComposingTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginAscii) {
const blink::WebString text = blink::WebString::FromASCII("a b");
TestImeCommitTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginUnicode) {
const blink::WebString text = blink::WebString::FromUTF16(u"さようなら");
TestImeCommitTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginEmpty) {
const blink::WebString text;
TestImeCommitTextForPlugin(text);
}
TEST_F(PdfViewWebPluginTest, ChangeTextSelection) {
ASSERT_FALSE(plugin_->HasSelection());
ASSERT_TRUE(plugin_->SelectionAsText().IsEmpty());
ASSERT_TRUE(plugin_->SelectionAsMarkup().IsEmpty());
static constexpr char kSelectedText[] = "1234";
EXPECT_CALL(*wrapper_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(*wrapper_ptr_,
TextSelectionChanged(blink::WebString::FromUTF8(kEmptyText), 0,
gfx::Range(0, 0)));
plugin_->SetSelectedText(kEmptyText);
EXPECT_FALSE(plugin_->HasSelection());
EXPECT_TRUE(plugin_->SelectionAsText().IsEmpty());
EXPECT_TRUE(plugin_->SelectionAsMarkup().IsEmpty());
}
TEST_F(PdfViewWebPluginTest, FormTextFieldFocusChangeUpdatesTextInputType) {
ASSERT_EQ(blink::WebTextInputType::kWebTextInputTypeNone,
wrapper_ptr_->widget_text_input_type());
MockFunction<void()> checkpoint;
{
InSequence sequence;
EXPECT_CALL(*wrapper_ptr_, UpdateTextInputState);
EXPECT_CALL(checkpoint, Call);
EXPECT_CALL(*wrapper_ptr_, UpdateTextInputState);
}
plugin_->FormTextFieldFocusChange(true);
EXPECT_EQ(blink::WebTextInputType::kWebTextInputTypeText,
wrapper_ptr_->widget_text_input_type());
checkpoint.Call();
plugin_->FormTextFieldFocusChange(false);
EXPECT_EQ(blink::WebTextInputType::kWebTextInputTypeNone,
wrapper_ptr_->widget_text_input_type());
}
TEST_F(PdfViewWebPluginTest, SearchString) {
static constexpr char16_t kPattern[] = u"fox";
static constexpr char16_t kTarget[] =
u"The quick brown fox jumped over the lazy Fox";
{
static constexpr PDFEngine::Client::SearchStringResult kExpectation[] = {
{16, 3}};
EXPECT_THAT(
plugin_->SearchString(kTarget, kPattern, /*case_sensitive=*/true),
Pointwise(SearchStringResultEq(), kExpectation));
}
{
static constexpr PDFEngine::Client::SearchStringResult kExpectation[] = {
{16, 3}, {41, 3}};
EXPECT_THAT(
plugin_->SearchString(kTarget, kPattern, /*case_sensitive=*/false),
Pointwise(SearchStringResultEq(), kExpectation));
}
}
TEST_F(PdfViewWebPluginTest, UpdateFocus) {
MockFunction<void(int checkpoint_num)> checkpoint;
{
InSequence sequence;
// Focus false -> true: Triggers updates.
EXPECT_CALL(*wrapper_ptr_, UpdateTextInputState);
EXPECT_CALL(*wrapper_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(*wrapper_ptr_, UpdateTextInputState).Times(2);
EXPECT_CALL(*wrapper_ptr_, UpdateSelectionBounds);
EXPECT_CALL(checkpoint, Call(3));
// Focus false -> false: No updates.
EXPECT_CALL(checkpoint, Call(4));
// Focus false -> true: Triggers updates.
EXPECT_CALL(*wrapper_ptr_, UpdateTextInputState);
EXPECT_CALL(*wrapper_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, CaretChangeUseZoomForDSFEnabled) {
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(true));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
UpdatePluginGeometry(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(12, 24, 36, 48));
plugin_->CaretChanged(gfx::Rect(10, 20, 30, 40));
EXPECT_EQ(gfx::Rect(28, 20, 30, 40), plugin_->GetPluginCaretBounds());
}
TEST_F(PdfViewWebPluginTest, CaretChangeUseZoomForDSFDisabled) {
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(false));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
UpdatePluginGeometry(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(12, 24, 36, 48));
plugin_->CaretChanged(gfx::Rect(10, 20, 30, 40));
EXPECT_EQ(gfx::Rect(23, 10, 15, 20), plugin_->GetPluginCaretBounds());
}
} // namespace chrome_pdf