blob: 009474ecc27dbe6a3ba3ad71276b4e942cb855f0 [file] [log] [blame]
// Copyright 2019 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/pdfium/pdfium_engine.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "pdf/document_attachment_info.h"
#include "pdf/document_layout.h"
#include "pdf/document_metadata.h"
#include "pdf/pdf_features.h"
#include "pdf/pdfium/pdfium_page.h"
#include "pdf/pdfium/pdfium_test_base.h"
#include "pdf/test/test_client.h"
#include "pdf/test/test_utils.h"
#include "ppapi/c/ppb_input_event.h"
#include "ppapi/cpp/size.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chrome_pdf {
namespace {
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
MATCHER_P2(LayoutWithSize, width, height, "") {
return arg.size() == pp::Size(width, height);
}
MATCHER_P(LayoutWithOptions, options, "") {
return arg.options() == options;
}
class MockTestClient : public TestClient {
public:
MockTestClient() {
ON_CALL(*this, ProposeDocumentLayout)
.WillByDefault([this](const DocumentLayout& layout) {
TestClient::ProposeDocumentLayout(layout);
});
}
// TODO(crbug.com/989095): MOCK_METHOD() triggers static_assert on Windows.
MOCK_METHOD1(ProposeDocumentLayout, void(const DocumentLayout& layout));
MOCK_METHOD1(ScrollToPage, void(int page));
};
class PDFiumEngineTest : public PDFiumTestBase {
protected:
void ExpectPageRect(PDFiumEngine* engine,
size_t page_index,
const pp::Rect& expected_rect) {
PDFiumPage* page = GetPDFiumPageForTest(engine, page_index);
ASSERT_TRUE(page);
CompareRect(expected_rect, page->rect());
}
};
TEST_F(PDFiumEngineTest, InitializeWithRectanglesMultiPagesPdf) {
NiceMock<MockTestClient> client;
// ProposeDocumentLayout() gets called twice during loading because
// PDFiumEngine::ContinueLoadingDocument() calls LoadBody() (which eventually
// triggers a layout proposal), and then calls FinishLoadingDocument() (since
// the document is complete), which calls LoadBody() again. Coalescing these
// proposals is not correct unless we address the issue covered by
// PDFiumEngineTest.ProposeDocumentLayoutWithOverlap.
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
.Times(2);
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(5, engine->GetNumberOfPages());
ExpectPageRect(engine.get(), 0, {38, 3, 266, 333});
ExpectPageRect(engine.get(), 1, {5, 350, 333, 266});
ExpectPageRect(engine.get(), 2, {38, 630, 266, 333});
ExpectPageRect(engine.get(), 3, {38, 977, 266, 333});
ExpectPageRect(engine.get(), 4, {38, 1324, 266, 333});
}
TEST_F(PDFiumEngineTest, InitializeWithRectanglesMultiPagesPdfInTwoUpView) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
ASSERT_TRUE(engine);
DocumentLayout::Options options;
options.set_two_up_view_enabled(true);
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithOptions(options)))
.WillOnce(Return());
engine->SetTwoUpView(true);
engine->ApplyDocumentLayout(options);
ASSERT_EQ(5, engine->GetNumberOfPages());
ExpectPageRect(engine.get(), 0, {72, 3, 266, 333});
ExpectPageRect(engine.get(), 1, {340, 3, 333, 266});
ExpectPageRect(engine.get(), 2, {72, 346, 266, 333});
ExpectPageRect(engine.get(), 3, {340, 346, 266, 333});
ExpectPageRect(engine.get(), 4, {68, 689, 266, 333});
}
TEST_F(PDFiumEngineTest, AppendBlankPagesWithFewerPages) {
NiceMock<MockTestClient> client;
{
InSequence normal_then_append;
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
.Times(2);
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(276, 1037)));
}
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
ASSERT_TRUE(engine);
engine->AppendBlankPages(3);
ASSERT_EQ(3, engine->GetNumberOfPages());
ExpectPageRect(engine.get(), 0, {5, 3, 266, 333});
ExpectPageRect(engine.get(), 1, {5, 350, 266, 333});
ExpectPageRect(engine.get(), 2, {5, 697, 266, 333});
}
TEST_F(PDFiumEngineTest, AppendBlankPagesWithMorePages) {
NiceMock<MockTestClient> client;
{
InSequence normal_then_append;
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
.Times(2);
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(276, 2425)));
}
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
ASSERT_TRUE(engine);
engine->AppendBlankPages(7);
ASSERT_EQ(7, engine->GetNumberOfPages());
ExpectPageRect(engine.get(), 0, {5, 3, 266, 333});
ExpectPageRect(engine.get(), 1, {5, 350, 266, 333});
ExpectPageRect(engine.get(), 2, {5, 697, 266, 333});
ExpectPageRect(engine.get(), 3, {5, 1044, 266, 333});
ExpectPageRect(engine.get(), 4, {5, 1391, 266, 333});
ExpectPageRect(engine.get(), 5, {5, 1738, 266, 333});
ExpectPageRect(engine.get(), 6, {5, 2085, 266, 333});
}
TEST_F(PDFiumEngineTest, ProposeDocumentLayoutWithOverlap) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
ASSERT_TRUE(engine);
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1463)))
.WillOnce(Return());
engine->RotateClockwise();
EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
.WillOnce(Return());
engine->RotateCounterclockwise();
}
TEST_F(PDFiumEngineTest, ApplyDocumentLayoutAvoidsInfiniteLoop) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
ASSERT_TRUE(engine);
DocumentLayout::Options options;
EXPECT_CALL(client, ScrollToPage(-1)).Times(0);
CompareSize({343, 1664}, engine->ApplyDocumentLayout(options));
options.RotatePagesClockwise();
EXPECT_CALL(client, ScrollToPage(-1)).Times(1);
CompareSize({343, 1463}, engine->ApplyDocumentLayout(options));
CompareSize({343, 1463}, engine->ApplyDocumentLayout(options));
}
TEST_F(PDFiumEngineTest, GetDocumentAttachmentInfo) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("embedded_attachments.pdf"));
ASSERT_TRUE(engine);
const std::vector<DocumentAttachmentInfo>& attachments =
engine->GetDocumentAttachmentInfoList();
ASSERT_EQ(3u, attachments.size());
{
const DocumentAttachmentInfo& attachment = attachments[0];
EXPECT_EQ("1.txt", base::UTF16ToUTF8(attachment.name));
EXPECT_EQ(4u, attachment.size_bytes);
EXPECT_EQ("D:20170712214438-07'00'",
base::UTF16ToUTF8(attachment.creation_date));
EXPECT_EQ("D:20160115091400", base::UTF16ToUTF8(attachment.modified_date));
}
{
const DocumentAttachmentInfo& attachment = attachments[1];
EXPECT_EQ("attached.pdf", base::UTF16ToUTF8(attachment.name));
EXPECT_EQ(5869u, attachment.size_bytes);
EXPECT_EQ("D:20170712214443-07'00'",
base::UTF16ToUTF8(attachment.creation_date));
EXPECT_EQ("D:20170712214410", base::UTF16ToUTF8(attachment.modified_date));
}
{
// Test attachments with no creation date or last modified date.
const DocumentAttachmentInfo& attachment = attachments[2];
EXPECT_EQ("附錄.txt", base::UTF16ToUTF8(attachment.name));
EXPECT_EQ(5u, attachment.size_bytes);
EXPECT_THAT(attachment.creation_date, IsEmpty());
EXPECT_THAT(attachment.modified_date, IsEmpty());
}
}
TEST_F(PDFiumEngineTest, NoDocumentAttachmentInfo) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
ASSERT_TRUE(engine);
EXPECT_EQ(0u, engine->GetDocumentAttachmentInfoList().size());
}
TEST_F(PDFiumEngineTest, GetDocumentMetadata) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("document_info.pdf"));
ASSERT_TRUE(engine);
const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
EXPECT_EQ(PdfVersion::k1_7, doc_metadata.version);
EXPECT_EQ("Sample PDF Document Info", doc_metadata.title);
EXPECT_EQ("Chromium Authors", doc_metadata.author);
EXPECT_EQ("Testing", doc_metadata.subject);
EXPECT_EQ("Your Preferred Text Editor", doc_metadata.creator);
EXPECT_EQ("fixup_pdf_template.py", doc_metadata.producer);
}
TEST_F(PDFiumEngineTest, GetEmptyDocumentMetadata) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
ASSERT_TRUE(engine);
const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
EXPECT_EQ(PdfVersion::k1_7, doc_metadata.version);
EXPECT_THAT(doc_metadata.title, IsEmpty());
EXPECT_THAT(doc_metadata.author, IsEmpty());
EXPECT_THAT(doc_metadata.subject, IsEmpty());
EXPECT_THAT(doc_metadata.creator, IsEmpty());
EXPECT_THAT(doc_metadata.producer, IsEmpty());
}
TEST_F(PDFiumEngineTest, GetBadPdfVersion) {
NiceMock<MockTestClient> client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("bad_version.pdf"));
ASSERT_TRUE(engine);
const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
EXPECT_EQ(PdfVersion::kUnknown, doc_metadata.version);
}
} // namespace
class TabbingTestClient : public TestClient {
public:
TabbingTestClient() = default;
~TabbingTestClient() override = default;
TabbingTestClient(const TabbingTestClient&) = delete;
TabbingTestClient& operator=(const TabbingTestClient&) = delete;
// Mock PDFEngine::Client methods.
MOCK_METHOD1(DocumentFocusChanged, void(bool));
};
class PDFiumEngineTabbingTest : public PDFiumTestBase {
public:
PDFiumEngineTabbingTest() = default;
~PDFiumEngineTabbingTest() override = default;
PDFiumEngineTabbingTest(const PDFiumEngineTabbingTest&) = delete;
PDFiumEngineTabbingTest& operator=(const PDFiumEngineTabbingTest&) = delete;
bool HandleTabEvent(PDFiumEngine* engine, uint32_t modifiers) {
return engine->HandleTabEvent(modifiers);
}
PDFiumEngine::FocusElementType GetFocusedElementType(PDFiumEngine* engine) {
return engine->focus_item_type_;
}
int GetLastFocusedPage(PDFiumEngine* engine) {
return engine->last_focused_page_;
}
PDFiumEngine::FocusElementType GetLastFocusedElementType(
PDFiumEngine* engine) {
return engine->last_focused_item_type_;
}
int GetLastFocusedAnnotationIndex(PDFiumEngine* engine) {
return engine->last_focused_annot_index_;
}
bool IsInFormTextArea(PDFiumEngine* engine) {
return engine->in_form_text_area_;
}
size_t GetSelectionSize(PDFiumEngine* engine) {
return engine->selection_.size();
}
const std::string& GetLinkUnderCursor(PDFiumEngine* engine) {
return engine->link_under_cursor_;
}
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(PDFiumEngineTabbingTest, LinkUnderCursorTest) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Widget annotation
* ++++ Widget annotation
* ++++ Highlight annotation
* ++++ Link annotation
*/
// Enable feature flag.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
chrome_pdf::features::kTabAcrossPDFAnnotations);
TestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
ASSERT_TRUE(engine);
// Initial value of link under cursor.
EXPECT_EQ("", GetLinkUnderCursor(engine.get()));
// Tab through non-link annotations and validate link under cursor.
for (int i = 0; i < 4; i++) {
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ("", GetLinkUnderCursor(engine.get()));
}
// Tab to Link annotation.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ("https://www.google.com/", GetLinkUnderCursor(engine.get()));
// Tab to previous annotation.
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ("", GetLinkUnderCursor(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, TabbingSupportedAnnots) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Widget annotation
* ++++ Widget annotation
* ++++ Highlight annotation
* ++++ Link annotation
*/
// Enable feature flag.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
chrome_pdf::features::kTabAcrossPDFAnnotations);
TestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(1, engine->GetNumberOfPages());
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, TabbingForwardTest) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Annotation
* ++++ Annotation
* ++ Page 2
* ++++ Annotation
*/
TabbingTestClient client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(2, engine->GetNumberOfPages());
static constexpr bool kExpectedFocusState[] = {true, false};
{
InSequence sequence;
for (auto focused : kExpectedFocusState)
EXPECT_CALL(client, DocumentFocusChanged(focused));
}
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, TabbingBackwardTest) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Annotation
* ++++ Annotation
* ++ Page 2
* ++++ Annotation
*/
TabbingTestClient client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(2, engine->GetNumberOfPages());
static constexpr bool kExpectedFocusState[] = {true, false};
{
InSequence sequence;
for (auto focused : kExpectedFocusState)
EXPECT_CALL(client, DocumentFocusChanged(focused));
}
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, TabbingWithModifiers) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Annotation
* ++++ Annotation
* ++ Page 2
* ++++ Annotation
*/
TestClient client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(2, engine->GetNumberOfPages());
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Tabbing with ctrl modifier.
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_CONTROLKEY));
// Tabbing with alt modifier.
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_ALTKEY));
// Tab to bring document into focus.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
// Tabbing with ctrl modifier.
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_CONTROLKEY));
// Tabbing with alt modifier.
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_ALTKEY));
// Tab to bring first page into focus.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
// Tabbing with ctrl modifier.
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_CONTROLKEY));
// Tabbing with alt modifier.
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_ALTKEY));
}
TEST_F(PDFiumEngineTabbingTest, NoFocusableItemTabbingTest) {
/*
* Document structure
* Document
* ++ Page 1
* ++ Page 2
*/
TabbingTestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(2, engine->GetNumberOfPages());
static constexpr bool kExpectedFocusState[] = {true, false, true, false};
{
InSequence sequence;
for (auto focused : kExpectedFocusState)
EXPECT_CALL(client, DocumentFocusChanged(focused));
}
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Tabbing forward.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
// Tabbing backward.
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, RestoringDocumentFocusTest) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Annotation
* ++++ Annotation
* ++ Page 2
* ++++ Annotation
*/
TabbingTestClient client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(2, engine->GetNumberOfPages());
static constexpr bool kExpectedFocusState[] = {true, false, true};
{
InSequence sequence;
for (auto focused : kExpectedFocusState)
EXPECT_CALL(client, DocumentFocusChanged(focused));
}
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Tabbing to bring the document into focus.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
engine->UpdateFocus(/*has_focus=*/false);
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetLastFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedAnnotationIndex(engine.get()));
engine->UpdateFocus(/*has_focus=*/true);
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, RestoringAnnotFocusTest) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Annotation
* ++++ Annotation
* ++ Page 2
* ++++ Annotation
*/
TabbingTestClient client;
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(2, engine->GetNumberOfPages());
static constexpr bool kExpectedFocusState[] = {true, false};
{
InSequence sequence;
for (auto focused : kExpectedFocusState)
EXPECT_CALL(client, DocumentFocusChanged(focused));
}
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Tabbing to bring last annotation of page 0 into focus.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
engine->UpdateFocus(/*has_focus=*/false);
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetLastFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedAnnotationIndex(engine.get()));
engine->UpdateFocus(/*has_focus=*/true);
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
// Tabbing now should bring the second page's annotation to focus.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, VerifyFormFieldStatesOnTabbing) {
/*
* Document structure
* Document
* ++ Page 1
* ++++ Annotation (Text Field)
* ++++ Annotation (Radio Button)
*/
TestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(1, engine->GetNumberOfPages());
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
// Bring focus to the text field.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
EXPECT_TRUE(IsInFormTextArea(engine.get()));
EXPECT_TRUE(engine->CanEditText());
// Bring focus to the button.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
EXPECT_FALSE(IsInFormTextArea(engine.get()));
EXPECT_FALSE(engine->CanEditText());
}
TEST_F(PDFiumEngineTabbingTest, ClearSelectionOnFocusInFormTextArea) {
TestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("form_text_fields.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(1, engine->GetNumberOfPages());
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Select all text.
engine->SelectAll();
EXPECT_EQ(1u, GetSelectionSize(engine.get()));
// Tab to bring focus to a form text area annotation.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
EXPECT_EQ(0u, GetSelectionSize(engine.get()));
}
TEST_F(PDFiumEngineTabbingTest, RetainSelectionOnFocusNotInFormTextArea) {
TestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
ASSERT_TRUE(engine);
ASSERT_EQ(1, engine->GetNumberOfPages());
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Select all text.
engine->SelectAll();
EXPECT_EQ(1u, GetSelectionSize(engine.get()));
// Tab to bring focus to a non form text area annotation (Button).
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
EXPECT_EQ(1u, GetSelectionSize(engine.get()));
}
class ScrollingTestClient : public TestClient {
public:
ScrollingTestClient() = default;
~ScrollingTestClient() override = default;
ScrollingTestClient(const ScrollingTestClient&) = delete;
ScrollingTestClient& operator=(const ScrollingTestClient&) = delete;
// Mock PDFEngine::Client methods.
MOCK_METHOD1(ScrollToX, void(int));
MOCK_METHOD2(ScrollToY, void(int, bool));
};
TEST_F(PDFiumEngineTabbingTest, MaintainViewportWhenFocusIsUpdated) {
StrictMock<ScrollingTestClient> 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(pp::Size(60, 40));
{
InSequence sequence;
static constexpr PP_Point kScrollValue = {510, 478};
EXPECT_CALL(client, ScrollToY(kScrollValue.y, false))
.WillOnce(Invoke(
[&engine]() { engine->ScrolledToYPosition(kScrollValue.y); }));
EXPECT_CALL(client, ScrollToX(kScrollValue.x)).WillOnce(Invoke([&engine]() {
engine->ScrolledToXPosition(kScrollValue.x);
}));
}
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
GetFocusedElementType(engine.get()));
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
// Tabbing to bring the document into focus.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
GetFocusedElementType(engine.get()));
// Tab to an annotation.
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
// Scroll focused annotation out of viewport.
static constexpr PP_Point kScrollPosition = {242, 746};
engine->ScrolledToXPosition(kScrollPosition.x);
engine->ScrolledToYPosition(kScrollPosition.y);
engine->UpdateFocus(/*has_focus=*/false);
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetLastFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(1, GetLastFocusedAnnotationIndex(engine.get()));
// Restore focus, we shouldn't have any calls to scroll viewport.
engine->UpdateFocus(/*has_focus=*/true);
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
GetFocusedElementType(engine.get()));
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
}
} // namespace chrome_pdf