
Take a step towards removing PDFEngine by first moving PDFEngine::Client out. Since pdfium_engine.h is quite big as-is, split PDFiumEngineClient into its own header file. Bug: 40128715 Low-Coverage-Reason: TRIVIAL_CHANGE just moving code around Change-Id: I3d2830e49a2ca5ceda3c7b921b155ae071f7f7eb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5714799 Reviewed-by: Alan Screen <awscreen@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Cr-Commit-Position: refs/heads/main@{#1329699}
289 lines
10 KiB
C++
289 lines
10 KiB
C++
// Copyright 2020 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/pdfium/pdfium_form_filler.h"
|
|
|
|
#include <vector>
|
|
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "build/build_config.h"
|
|
#include "gin/public/isolate_holder.h"
|
|
#include "pdf/pdfium/pdfium_engine.h"
|
|
#include "pdf/pdfium/pdfium_test_base.h"
|
|
#include "pdf/test/test_client.h"
|
|
#include "pdf/test/test_helpers.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "third_party/blink/public/common/input/web_input_event.h"
|
|
#include "third_party/pdfium/public/fpdf_annot.h"
|
|
#include "third_party/pdfium/public/fpdf_formfill.h"
|
|
#include "ui/gfx/geometry/point.h"
|
|
#include "ui/gfx/geometry/size.h"
|
|
#include "v8/include/v8-isolate.h"
|
|
|
|
namespace chrome_pdf {
|
|
|
|
namespace {
|
|
|
|
using ::testing::Contains;
|
|
using ::testing::InSequence;
|
|
using ::testing::Return;
|
|
|
|
class FormFillerTestClient : public TestClient {
|
|
public:
|
|
FormFillerTestClient() = default;
|
|
~FormFillerTestClient() override = default;
|
|
FormFillerTestClient(const FormFillerTestClient&) = delete;
|
|
FormFillerTestClient& operator=(const FormFillerTestClient&) = delete;
|
|
|
|
// Mock PDFiumEngineClient methods.
|
|
MOCK_METHOD(void, Beep, (), (override));
|
|
MOCK_METHOD(std::string, GetURL, (), (override));
|
|
MOCK_METHOD(void, ScrollToX, (int), (override));
|
|
MOCK_METHOD(void, ScrollToY, (int), (override));
|
|
MOCK_METHOD(void,
|
|
NavigateTo,
|
|
(const std::string&, WindowOpenDisposition),
|
|
(override));
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class FormFillerTest : public PDFiumTestBase {
|
|
public:
|
|
FormFillerTest() = default;
|
|
~FormFillerTest() override = default;
|
|
FormFillerTest(const FormFillerTest&) = delete;
|
|
FormFillerTest& operator=(const FormFillerTest&) = delete;
|
|
|
|
void TriggerFormFocusChange(PDFiumEngine* engine,
|
|
FPDF_ANNOTATION annot,
|
|
int page_index) {
|
|
ASSERT_TRUE(engine);
|
|
engine->form_filler_.Form_OnFocusChange(&engine->form_filler_, annot,
|
|
page_index);
|
|
}
|
|
|
|
void TriggerDoURIActionWithKeyboardModifier(PDFiumEngine* engine,
|
|
FPDF_BYTESTRING uri,
|
|
int modifiers) {
|
|
ASSERT_TRUE(engine);
|
|
engine->form_filler_.Form_DoURIActionWithKeyboardModifier(
|
|
&engine->form_filler_, uri, modifiers);
|
|
}
|
|
|
|
#if defined(PDF_ENABLE_V8)
|
|
void TriggerBeep(PDFiumEngine* engine) {
|
|
ASSERT_TRUE(engine);
|
|
engine->form_filler_.Form_Beep(&engine->form_filler_,
|
|
JSPLATFORM_BEEP_DEFAULT);
|
|
}
|
|
|
|
int TriggerGetFilePath(PDFiumEngine& engine, void* file_path, int length) {
|
|
return engine.form_filler_.Form_GetFilePath(&engine.form_filler_, file_path,
|
|
length);
|
|
}
|
|
#endif // defined(PDF_ENABLE_V8)
|
|
};
|
|
|
|
TEST_P(FormFillerTest, DoURIActionWithKeyboardModifier) {
|
|
FormFillerTestClient client;
|
|
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
|
|
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
|
|
ASSERT_TRUE(engine);
|
|
|
|
const char kUri[] = "https://www.google.com/";
|
|
{
|
|
InSequence sequence;
|
|
EXPECT_CALL(client, NavigateTo(kUri, WindowOpenDisposition::CURRENT_TAB))
|
|
.Times(1);
|
|
EXPECT_CALL(client, NavigateTo(kUri, WindowOpenDisposition::SAVE_TO_DISK))
|
|
.Times(1);
|
|
EXPECT_CALL(client,
|
|
NavigateTo(kUri, WindowOpenDisposition::NEW_BACKGROUND_TAB))
|
|
.Times(1);
|
|
EXPECT_CALL(client, NavigateTo(kUri, WindowOpenDisposition::NEW_WINDOW))
|
|
.Times(1);
|
|
EXPECT_CALL(client,
|
|
NavigateTo(kUri, WindowOpenDisposition::NEW_FOREGROUND_TAB))
|
|
.Times(1);
|
|
EXPECT_CALL(client,
|
|
NavigateTo(kUri, WindowOpenDisposition::NEW_BACKGROUND_TAB))
|
|
.Times(1);
|
|
EXPECT_CALL(client,
|
|
NavigateTo(kUri, WindowOpenDisposition::NEW_FOREGROUND_TAB))
|
|
.Times(1);
|
|
}
|
|
|
|
constexpr blink::WebInputEvent::Modifiers kModifierKey =
|
|
#if BUILDFLAG(IS_MAC)
|
|
blink::WebInputEvent::Modifiers::kMetaKey;
|
|
#else
|
|
blink::WebInputEvent::Modifiers::kControlKey;
|
|
#endif
|
|
|
|
int modifiers = 0;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
modifiers = blink::WebInputEvent::Modifiers::kAltKey;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
modifiers = kModifierKey;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
modifiers = blink::WebInputEvent::Modifiers::kShiftKey;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
modifiers |= kModifierKey;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
modifiers = blink::WebInputEvent::Modifiers::kMiddleButtonDown;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
modifiers |= blink::WebInputEvent::Modifiers::kShiftKey;
|
|
TriggerDoURIActionWithKeyboardModifier(engine.get(), kUri, modifiers);
|
|
}
|
|
|
|
TEST_P(FormFillerTest, FormOnFocusChange) {
|
|
struct {
|
|
// Initial scroll position of the document.
|
|
gfx::Point initial_position;
|
|
// Page number on which the annotation is present.
|
|
int page_index;
|
|
// The index of test annotation on page_index.
|
|
int annot_index;
|
|
// The scroll position to bring the annotation into view. (0,0) if the
|
|
// annotation is already in view.
|
|
gfx::Point final_scroll_position;
|
|
} static constexpr test_cases[] = {
|
|
{{0, 0}, 0, 0, {242, 746}}, {{0, 0}, 0, 1, {510, 478}},
|
|
{{242, 40}, 0, 0, {0, 746}}, {{60, 758}, 0, 0, {242, 0}},
|
|
{{242, 758}, 0, 0, {0, 0}}, {{242, 768}, 0, 0, {0, 746}},
|
|
{{274, 758}, 0, 0, {242, 0}}, {{60, 40}, 1, 0, {242, 1816}}};
|
|
|
|
FormFillerTestClient client;
|
|
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
|
|
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
|
|
ASSERT_TRUE(engine);
|
|
ASSERT_EQ(2, engine->GetNumberOfPages());
|
|
engine->PluginSizeUpdated(gfx::Size(60, 40));
|
|
|
|
{
|
|
InSequence sequence;
|
|
|
|
for (const auto& test_case : test_cases) {
|
|
if (test_case.final_scroll_position.y() != 0) {
|
|
EXPECT_CALL(client, ScrollToY(test_case.final_scroll_position.y()));
|
|
}
|
|
if (test_case.final_scroll_position.x() != 0)
|
|
EXPECT_CALL(client, ScrollToX(test_case.final_scroll_position.x()));
|
|
}
|
|
}
|
|
|
|
for (const auto& test_case : test_cases) {
|
|
// Setting up the initial scroll positions.
|
|
engine->ScrolledToXPosition(test_case.initial_position.x());
|
|
engine->ScrolledToYPosition(test_case.initial_position.y());
|
|
|
|
PDFiumPage& page = GetPDFiumPageForTest(*engine, test_case.page_index);
|
|
ScopedFPDFAnnotation annot(
|
|
FPDFPage_GetAnnot(page.GetPage(), test_case.annot_index));
|
|
ASSERT_TRUE(annot);
|
|
TriggerFormFocusChange(engine.get(), annot.get(), test_case.page_index);
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All, FormFillerTest, testing::Bool());
|
|
|
|
#if defined(PDF_ENABLE_V8)
|
|
class FormFillerJavaScriptTest : public FormFillerTest {
|
|
public:
|
|
void SetUp() override {
|
|
// Needed for setting up V8.
|
|
//
|
|
// Note that this does not call FormFillerTest::SetUp() to avoid double SDK
|
|
// initialization.
|
|
InitializeSDK(/*enable_v8=*/true, /*use_skia=*/GetParam(),
|
|
FontMappingMode::kNoMapping);
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Note that this does not call FormFillerTest::TearDown() to avoid double
|
|
// SDK destruction.
|
|
ShutdownSDK();
|
|
}
|
|
};
|
|
|
|
TEST_P(FormFillerJavaScriptTest, IsolateScoping) {
|
|
// Enter the embedder's isolate so it can be captured when the
|
|
// `PDFiumFormFiller` is created.
|
|
v8::Isolate* embedder_isolate = GetBlinkIsolate();
|
|
v8::Isolate::Scope embedder_isolate_scope(embedder_isolate);
|
|
|
|
FormFillerTestClient client;
|
|
PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
|
|
|
|
gin::IsolateHolder pdfium_test_isolate_holder(
|
|
base::SingleThreadTaskRunner::GetCurrentDefault(),
|
|
gin::IsolateHolder::IsolateType::kTest);
|
|
v8::Isolate* pdfium_test_isolate = pdfium_test_isolate_holder.isolate();
|
|
|
|
// Enter PDFium's isolate and then trigger a beep callback. The embedder's
|
|
// isolate should be entered during the callback's execution.
|
|
v8::Isolate::Scope pdfium_test_isolate_scope(pdfium_test_isolate);
|
|
EXPECT_CALL(client, Beep).WillOnce([&embedder_isolate]() {
|
|
EXPECT_EQ(v8::Isolate::TryGetCurrent(), embedder_isolate);
|
|
});
|
|
TriggerBeep(&engine);
|
|
|
|
// PDFium's isolate should be entered again after the callback completes.
|
|
EXPECT_EQ(v8::Isolate::TryGetCurrent(), pdfium_test_isolate);
|
|
}
|
|
|
|
TEST_P(FormFillerJavaScriptTest, GetFilePath) {
|
|
constexpr char kTestPath[] = "https://www.example.com/path/to/the.pdf";
|
|
constexpr int kTestPathSize = static_cast<int>(std::size(kTestPath));
|
|
|
|
FormFillerTestClient client;
|
|
EXPECT_CALL(client, GetURL).Times(2).WillRepeatedly(Return(kTestPath));
|
|
PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
|
|
|
|
EXPECT_EQ(TriggerGetFilePath(engine, /*file_path=*/nullptr, /*length=*/0),
|
|
kTestPathSize);
|
|
|
|
std::vector<char> buffer(kTestPathSize, 'X');
|
|
EXPECT_EQ(TriggerGetFilePath(engine, buffer.data(), buffer.size()),
|
|
kTestPathSize);
|
|
EXPECT_STREQ(buffer.data(), kTestPath);
|
|
}
|
|
|
|
TEST_P(FormFillerJavaScriptTest, GetFilePathEmpty) {
|
|
FormFillerTestClient client;
|
|
EXPECT_CALL(client, GetURL).Times(2).WillRepeatedly(Return(std::string()));
|
|
PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
|
|
|
|
EXPECT_EQ(TriggerGetFilePath(engine, /*file_path=*/nullptr, /*length=*/0), 1);
|
|
|
|
char buffer[] = "buffer";
|
|
EXPECT_EQ(TriggerGetFilePath(engine, buffer, /*length=*/1), 1);
|
|
|
|
// The trailing null should be copied over.
|
|
EXPECT_STREQ(buffer, "");
|
|
}
|
|
|
|
TEST_P(FormFillerJavaScriptTest, GetFilePathShortBuffer) {
|
|
constexpr char kTestPath[] = "https://www.example.com/path/to/the.pdf";
|
|
constexpr int kTestPathSize = static_cast<int>(std::size(kTestPath));
|
|
|
|
FormFillerTestClient client;
|
|
EXPECT_CALL(client, GetURL).WillRepeatedly(Return(kTestPath));
|
|
PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
|
|
|
|
std::vector<char> buffer(kTestPathSize - 1, 'X');
|
|
EXPECT_EQ(TriggerGetFilePath(engine, buffer.data(), buffer.size()),
|
|
kTestPathSize);
|
|
|
|
// Nothing should be copied over. The buffer size is too small to contain a
|
|
// trailing null.
|
|
EXPECT_THAT(buffer, Contains('X').Times(buffer.size()));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All, FormFillerJavaScriptTest, testing::Bool());
|
|
#endif // defined(PDF_ENABLE_V8)
|
|
|
|
} // namespace chrome_pdf
|