1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "pdf/pdfium/pdfium_engine.h"
6
7 #include <stdint.h>
8
9 #include "base/hash/md5.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/test/gtest_util.h"
12 #include "base/test/mock_callback.h"
13 #include "base/test/scoped_feature_list.h"
14 #include "base/test/task_environment.h"
15 #include "pdf/document_attachment_info.h"
16 #include "pdf/document_layout.h"
17 #include "pdf/document_metadata.h"
18 #include "pdf/pdf_features.h"
19 #include "pdf/pdfium/pdfium_page.h"
20 #include "pdf/pdfium/pdfium_test_base.h"
21 #include "pdf/ppapi_migration/input_event_conversions.h"
22 #include "pdf/test/test_client.h"
23 #include "pdf/test/test_document_loader.h"
24 #include "pdf/thumbnail.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "ui/gfx/geometry/point.h"
28 #include "ui/gfx/geometry/rect.h"
29 #include "ui/gfx/geometry/size.h"
30
31 namespace chrome_pdf {
32
33 namespace {
34
35 using ::testing::InSequence;
36 using ::testing::Invoke;
37 using ::testing::IsEmpty;
38 using ::testing::NiceMock;
39 using ::testing::Return;
40 using ::testing::StrictMock;
41
42 MATCHER_P2(LayoutWithSize, width, height, "") {
43 return arg.size() == gfx::Size(width, height);
44 }
45
46 MATCHER_P(LayoutWithOptions, options, "") {
47 return arg.options() == options;
48 }
49
50 class MockTestClient : public TestClient {
51 public:
MockTestClient()52 MockTestClient() {
53 ON_CALL(*this, ProposeDocumentLayout)
54 .WillByDefault([this](const DocumentLayout& layout) {
55 TestClient::ProposeDocumentLayout(layout);
56 });
57 }
58
59 MOCK_METHOD(void,
60 ProposeDocumentLayout,
61 (const DocumentLayout& layout),
62 (override));
63 MOCK_METHOD(void, ScrollToPage, (int page), (override));
64 };
65
66 } // namespace
67
68 class PDFiumEngineTest : public PDFiumTestBase {
69 protected:
ExpectPageRect(const PDFiumEngine & engine,size_t page_index,const gfx::Rect & expected_rect)70 void ExpectPageRect(const PDFiumEngine& engine,
71 size_t page_index,
72 const gfx::Rect& expected_rect) {
73 const PDFiumPage& page = GetPDFiumPageForTest(engine, page_index);
74 EXPECT_EQ(expected_rect, page.rect());
75 }
76
77 // Tries to load a PDF incrementally, returning `true` if the PDF actually was
78 // loaded incrementally. Note that this function will return `false` if
79 // incremental loading fails, but also if incremental loading is disabled.
TryLoadIncrementally()80 bool TryLoadIncrementally() {
81 NiceMock<MockTestClient> client;
82 InitializeEngineResult initialize_result = InitializeEngineWithoutLoading(
83 &client, FILE_PATH_LITERAL("linearized.pdf"));
84 if (!initialize_result.engine) {
85 ADD_FAILURE();
86 return false;
87 }
88 PDFiumEngine& engine = *initialize_result.engine;
89
90 // Load enough for the document to become partially available.
91 initialize_result.document_loader->SimulateLoadData(8192);
92
93 bool loaded_incrementally;
94 if (engine.GetNumberOfPages() == 0) {
95 // This is not necessarily a test failure; it just indicates incremental
96 // loading is not occurring.
97 loaded_incrementally = false;
98 } else {
99 // Note: Plugin size chosen so all pages of the document are visible. The
100 // engine only updates availability incrementally for visible pages.
101 EXPECT_EQ(0, CountAvailablePages(engine));
102 engine.PluginSizeUpdated({1024, 4096});
103 int available_pages = CountAvailablePages(engine);
104 loaded_incrementally =
105 0 < available_pages && available_pages < engine.GetNumberOfPages();
106 }
107
108 // Verify that loading can finish.
109 while (initialize_result.document_loader->SimulateLoadData(UINT32_MAX))
110 continue;
111
112 EXPECT_EQ(engine.GetNumberOfPages(), CountAvailablePages(engine));
113
114 return loaded_incrementally;
115 }
116
117 // Counts the number of available pages. Returns `int` instead of `size_t` for
118 // consistency with `PDFiumEngine::GetNumberOfPages()`.
CountAvailablePages(const PDFiumEngine & engine)119 int CountAvailablePages(const PDFiumEngine& engine) {
120 int available_pages = 0;
121 for (int i = 0; i < engine.GetNumberOfPages(); ++i) {
122 if (GetPDFiumPageForTest(engine, i).available())
123 ++available_pages;
124 }
125 return available_pages;
126 }
127 };
128
TEST_F(PDFiumEngineTest,InitializeWithRectanglesMultiPagesPdf)129 TEST_F(PDFiumEngineTest, InitializeWithRectanglesMultiPagesPdf) {
130 NiceMock<MockTestClient> client;
131
132 // ProposeDocumentLayout() gets called twice during loading because
133 // PDFiumEngine::ContinueLoadingDocument() calls LoadBody() (which eventually
134 // triggers a layout proposal), and then calls FinishLoadingDocument() (since
135 // the document is complete), which calls LoadBody() again. Coalescing these
136 // proposals is not correct unless we address the issue covered by
137 // PDFiumEngineTest.ProposeDocumentLayoutWithOverlap.
138 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
139 .Times(2);
140
141 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
142 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
143 ASSERT_TRUE(engine);
144 ASSERT_EQ(5, engine->GetNumberOfPages());
145
146 ExpectPageRect(*engine, 0, {38, 3, 266, 333});
147 ExpectPageRect(*engine, 1, {5, 350, 333, 266});
148 ExpectPageRect(*engine, 2, {38, 630, 266, 333});
149 ExpectPageRect(*engine, 3, {38, 977, 266, 333});
150 ExpectPageRect(*engine, 4, {38, 1324, 266, 333});
151 }
152
TEST_F(PDFiumEngineTest,InitializeWithRectanglesMultiPagesPdfInTwoUpView)153 TEST_F(PDFiumEngineTest, InitializeWithRectanglesMultiPagesPdfInTwoUpView) {
154 NiceMock<MockTestClient> client;
155 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
156 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
157 ASSERT_TRUE(engine);
158
159 DocumentLayout::Options options;
160 options.set_two_up_view_enabled(true);
161 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithOptions(options)))
162 .WillOnce(Return());
163 engine->SetTwoUpView(true);
164
165 engine->ApplyDocumentLayout(options);
166
167 ASSERT_EQ(5, engine->GetNumberOfPages());
168
169 ExpectPageRect(*engine, 0, {72, 3, 266, 333});
170 ExpectPageRect(*engine, 1, {340, 3, 333, 266});
171 ExpectPageRect(*engine, 2, {72, 346, 266, 333});
172 ExpectPageRect(*engine, 3, {340, 346, 266, 333});
173 ExpectPageRect(*engine, 4, {68, 689, 266, 333});
174 }
175
TEST_F(PDFiumEngineTest,AppendBlankPagesWithFewerPages)176 TEST_F(PDFiumEngineTest, AppendBlankPagesWithFewerPages) {
177 NiceMock<MockTestClient> client;
178 {
179 InSequence normal_then_append;
180 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
181 .Times(2);
182 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(276, 1037)));
183 }
184
185 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
186 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
187 ASSERT_TRUE(engine);
188
189 engine->AppendBlankPages(3);
190 ASSERT_EQ(3, engine->GetNumberOfPages());
191
192 ExpectPageRect(*engine, 0, {5, 3, 266, 333});
193 ExpectPageRect(*engine, 1, {5, 350, 266, 333});
194 ExpectPageRect(*engine, 2, {5, 697, 266, 333});
195 }
196
TEST_F(PDFiumEngineTest,AppendBlankPagesWithMorePages)197 TEST_F(PDFiumEngineTest, AppendBlankPagesWithMorePages) {
198 NiceMock<MockTestClient> client;
199 {
200 InSequence normal_then_append;
201 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
202 .Times(2);
203 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(276, 2425)));
204 }
205
206 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
207 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
208 ASSERT_TRUE(engine);
209
210 engine->AppendBlankPages(7);
211 ASSERT_EQ(7, engine->GetNumberOfPages());
212
213 ExpectPageRect(*engine, 0, {5, 3, 266, 333});
214 ExpectPageRect(*engine, 1, {5, 350, 266, 333});
215 ExpectPageRect(*engine, 2, {5, 697, 266, 333});
216 ExpectPageRect(*engine, 3, {5, 1044, 266, 333});
217 ExpectPageRect(*engine, 4, {5, 1391, 266, 333});
218 ExpectPageRect(*engine, 5, {5, 1738, 266, 333});
219 ExpectPageRect(*engine, 6, {5, 2085, 266, 333});
220 }
221
TEST_F(PDFiumEngineTest,ProposeDocumentLayoutWithOverlap)222 TEST_F(PDFiumEngineTest, ProposeDocumentLayoutWithOverlap) {
223 NiceMock<MockTestClient> client;
224 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
225 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
226 ASSERT_TRUE(engine);
227
228 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1463)))
229 .WillOnce(Return());
230 engine->RotateClockwise();
231
232 EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithSize(343, 1664)))
233 .WillOnce(Return());
234 engine->RotateCounterclockwise();
235 }
236
TEST_F(PDFiumEngineTest,ApplyDocumentLayoutAvoidsInfiniteLoop)237 TEST_F(PDFiumEngineTest, ApplyDocumentLayoutAvoidsInfiniteLoop) {
238 NiceMock<MockTestClient> client;
239 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
240 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
241 ASSERT_TRUE(engine);
242
243 DocumentLayout::Options options;
244 EXPECT_CALL(client, ScrollToPage(-1)).Times(0);
245 EXPECT_EQ(gfx::Size(343, 1664), engine->ApplyDocumentLayout(options));
246
247 options.RotatePagesClockwise();
248 EXPECT_CALL(client, ScrollToPage(-1)).Times(1);
249 EXPECT_EQ(gfx::Size(343, 1463), engine->ApplyDocumentLayout(options));
250 EXPECT_EQ(gfx::Size(343, 1463), engine->ApplyDocumentLayout(options));
251 }
252
TEST_F(PDFiumEngineTest,GetDocumentAttachments)253 TEST_F(PDFiumEngineTest, GetDocumentAttachments) {
254 NiceMock<MockTestClient> client;
255 std::unique_ptr<PDFiumEngine> engine =
256 InitializeEngine(&client, FILE_PATH_LITERAL("embedded_attachments.pdf"));
257 ASSERT_TRUE(engine);
258
259 const std::vector<DocumentAttachmentInfo>& attachments =
260 engine->GetDocumentAttachmentInfoList();
261 ASSERT_EQ(3u, attachments.size());
262
263 {
264 const DocumentAttachmentInfo& attachment = attachments[0];
265 EXPECT_EQ("1.txt", base::UTF16ToUTF8(attachment.name));
266 EXPECT_TRUE(attachment.is_readable);
267 EXPECT_EQ(4u, attachment.size_bytes);
268 EXPECT_EQ("D:20170712214438-07'00'",
269 base::UTF16ToUTF8(attachment.creation_date));
270 EXPECT_EQ("D:20160115091400", base::UTF16ToUTF8(attachment.modified_date));
271
272 std::vector<uint8_t> content = engine->GetAttachmentData(0);
273 ASSERT_EQ(attachment.size_bytes, content.size());
274 std::string content_str(content.begin(), content.end());
275 EXPECT_EQ("test", content_str);
276 }
277
278 {
279 static constexpr char kCheckSum[] = "72afcddedf554dda63c0c88e06f1ce18";
280 const DocumentAttachmentInfo& attachment = attachments[1];
281 EXPECT_EQ("attached.pdf", base::UTF16ToUTF8(attachment.name));
282 EXPECT_TRUE(attachment.is_readable);
283 EXPECT_EQ(5869u, attachment.size_bytes);
284 EXPECT_EQ("D:20170712214443-07'00'",
285 base::UTF16ToUTF8(attachment.creation_date));
286 EXPECT_EQ("D:20170712214410", base::UTF16ToUTF8(attachment.modified_date));
287
288 std::vector<uint8_t> content = engine->GetAttachmentData(1);
289 ASSERT_EQ(attachment.size_bytes, content.size());
290 // The whole attachment content is too long to do string comparison.
291 // Instead, we only verify the checksum value here.
292 base::MD5Digest hash;
293 base::MD5Sum(content.data(), content.size(), &hash);
294 EXPECT_EQ(kCheckSum, base::MD5DigestToBase16(hash));
295 }
296
297 {
298 // Test attachments with no creation date or last modified date.
299 const DocumentAttachmentInfo& attachment = attachments[2];
300 EXPECT_EQ("附錄.txt", base::UTF16ToUTF8(attachment.name));
301 EXPECT_TRUE(attachment.is_readable);
302 EXPECT_EQ(5u, attachment.size_bytes);
303 EXPECT_THAT(attachment.creation_date, IsEmpty());
304 EXPECT_THAT(attachment.modified_date, IsEmpty());
305
306 std::vector<uint8_t> content = engine->GetAttachmentData(2);
307 ASSERT_EQ(attachment.size_bytes, content.size());
308 std::string content_str(content.begin(), content.end());
309 EXPECT_EQ("test\n", content_str);
310 }
311 }
312
TEST_F(PDFiumEngineTest,DocumentWithInvalidAttachment)313 TEST_F(PDFiumEngineTest, DocumentWithInvalidAttachment) {
314 NiceMock<MockTestClient> client;
315 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
316 &client, FILE_PATH_LITERAL("embedded_attachments_invalid_data.pdf"));
317 ASSERT_TRUE(engine);
318
319 const std::vector<DocumentAttachmentInfo>& attachments =
320 engine->GetDocumentAttachmentInfoList();
321 ASSERT_EQ(1u, attachments.size());
322
323 // Test on an attachment which FPDFAttachment_GetFile() fails to retrieve data
324 // from.
325 const DocumentAttachmentInfo& attachment = attachments[0];
326 EXPECT_EQ("1.txt", base::UTF16ToUTF8(attachment.name));
327 EXPECT_FALSE(attachment.is_readable);
328 EXPECT_EQ(0u, attachment.size_bytes);
329 EXPECT_THAT(attachment.creation_date, IsEmpty());
330 EXPECT_THAT(attachment.modified_date, IsEmpty());
331 }
332
TEST_F(PDFiumEngineTest,NoDocumentAttachmentInfo)333 TEST_F(PDFiumEngineTest, NoDocumentAttachmentInfo) {
334 NiceMock<MockTestClient> client;
335 std::unique_ptr<PDFiumEngine> engine =
336 InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
337 ASSERT_TRUE(engine);
338
339 EXPECT_EQ(0u, engine->GetDocumentAttachmentInfoList().size());
340 }
341
TEST_F(PDFiumEngineTest,GetDocumentMetadata)342 TEST_F(PDFiumEngineTest, GetDocumentMetadata) {
343 NiceMock<MockTestClient> client;
344 std::unique_ptr<PDFiumEngine> engine =
345 InitializeEngine(&client, FILE_PATH_LITERAL("document_info.pdf"));
346 ASSERT_TRUE(engine);
347
348 const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
349
350 EXPECT_EQ(PdfVersion::k1_7, doc_metadata.version);
351 EXPECT_EQ("Sample PDF Document Info", doc_metadata.title);
352 EXPECT_EQ("Chromium Authors", doc_metadata.author);
353 EXPECT_EQ("Testing", doc_metadata.subject);
354 EXPECT_EQ("Your Preferred Text Editor", doc_metadata.creator);
355 EXPECT_EQ("fixup_pdf_template.py", doc_metadata.producer);
356 }
357
TEST_F(PDFiumEngineTest,GetEmptyDocumentMetadata)358 TEST_F(PDFiumEngineTest, GetEmptyDocumentMetadata) {
359 NiceMock<MockTestClient> client;
360 std::unique_ptr<PDFiumEngine> engine =
361 InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
362 ASSERT_TRUE(engine);
363
364 const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
365
366 EXPECT_EQ(PdfVersion::k1_7, doc_metadata.version);
367 EXPECT_THAT(doc_metadata.title, IsEmpty());
368 EXPECT_THAT(doc_metadata.author, IsEmpty());
369 EXPECT_THAT(doc_metadata.subject, IsEmpty());
370 EXPECT_THAT(doc_metadata.creator, IsEmpty());
371 EXPECT_THAT(doc_metadata.producer, IsEmpty());
372 }
373
TEST_F(PDFiumEngineTest,GetBadPdfVersion)374 TEST_F(PDFiumEngineTest, GetBadPdfVersion) {
375 NiceMock<MockTestClient> client;
376 std::unique_ptr<PDFiumEngine> engine =
377 InitializeEngine(&client, FILE_PATH_LITERAL("bad_version.pdf"));
378 ASSERT_TRUE(engine);
379
380 const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
381 EXPECT_EQ(PdfVersion::kUnknown, doc_metadata.version);
382 }
383
TEST_F(PDFiumEngineTest,IncrementalLoadingFeatureDefault)384 TEST_F(PDFiumEngineTest, IncrementalLoadingFeatureDefault) {
385 EXPECT_FALSE(TryLoadIncrementally());
386 }
387
TEST_F(PDFiumEngineTest,IncrementalLoadingFeatureEnabled)388 TEST_F(PDFiumEngineTest, IncrementalLoadingFeatureEnabled) {
389 base::test::ScopedFeatureList scoped_feature_list;
390 scoped_feature_list.InitAndEnableFeature(features::kPdfIncrementalLoading);
391 EXPECT_TRUE(TryLoadIncrementally());
392 }
393
TEST_F(PDFiumEngineTest,IncrementalLoadingFeatureDisabled)394 TEST_F(PDFiumEngineTest, IncrementalLoadingFeatureDisabled) {
395 base::test::ScopedFeatureList scoped_feature_list;
396 scoped_feature_list.InitAndDisableFeature(features::kPdfIncrementalLoading);
397 EXPECT_FALSE(TryLoadIncrementally());
398 }
399
TEST_F(PDFiumEngineTest,RequestThumbnail)400 TEST_F(PDFiumEngineTest, RequestThumbnail) {
401 TestClient client;
402 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
403 &client, FILE_PATH_LITERAL("rectangles_multi_pages.pdf"));
404 ASSERT_TRUE(engine);
405
406 const int num_pages = engine->GetNumberOfPages();
407 ASSERT_EQ(5, num_pages);
408 ASSERT_EQ(num_pages, CountAvailablePages(*engine));
409
410 // Each page should immediately return a thumbnail.
411 for (int i = 0; i < num_pages; ++i) {
412 base::MockCallback<SendThumbnailCallback> send_callback;
413 EXPECT_CALL(send_callback, Run);
414 engine->RequestThumbnail(/*page_index=*/i, /*device_pixel_ratio=*/1,
415 send_callback.Get());
416 }
417 }
418
TEST_F(PDFiumEngineTest,RequestThumbnailLinearized)419 TEST_F(PDFiumEngineTest, RequestThumbnailLinearized) {
420 base::test::ScopedFeatureList scoped_feature_list;
421 scoped_feature_list.InitAndEnableFeature(features::kPdfIncrementalLoading);
422
423 NiceMock<MockTestClient> client;
424 InitializeEngineResult initialize_result = InitializeEngineWithoutLoading(
425 &client, FILE_PATH_LITERAL("linearized.pdf"));
426 ASSERT_TRUE(initialize_result.engine);
427 PDFiumEngine& engine = *initialize_result.engine;
428
429 // Load only some pages.
430 initialize_result.document_loader->SimulateLoadData(8192);
431
432 // Note: Plugin size chosen so all pages of the document are visible. The
433 // engine only updates availability incrementally for visible pages.
434 engine.PluginSizeUpdated({1024, 4096});
435
436 const int num_pages = engine.GetNumberOfPages();
437 ASSERT_EQ(3, num_pages);
438 const int available_pages = CountAvailablePages(engine);
439 ASSERT_LT(0, available_pages);
440 ASSERT_GT(num_pages, available_pages);
441
442 // Initialize callbacks for first and last pages.
443 base::MockCallback<SendThumbnailCallback> first_loaded;
444 base::MockCallback<SendThumbnailCallback> last_loaded;
445
446 // When the document is partially loaded, `SendThumbnailCallback` is only run
447 // for the loaded page even though `RequestThumbnail()` gets called for both
448 // pages.
449 EXPECT_CALL(first_loaded, Run);
450 engine.RequestThumbnail(/*page_index=*/0, /*device_pixel_ratio=*/1,
451 first_loaded.Get());
452 engine.RequestThumbnail(/*page_index=*/num_pages - 1,
453 /*device_pixel_ratio=*/1, last_loaded.Get());
454
455 // Finish loading the document. `SendThumbnailCallback` should be run for the
456 // last page.
457 EXPECT_CALL(last_loaded, Run);
458 while (initialize_result.document_loader->SimulateLoadData(UINT32_MAX))
459 continue;
460 }
461
462 using PDFiumEngineDeathTest = PDFiumEngineTest;
463
TEST_F(PDFiumEngineDeathTest,RequestThumbnailRedundant)464 TEST_F(PDFiumEngineDeathTest, RequestThumbnailRedundant) {
465 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
466 base::test::ScopedFeatureList scoped_feature_list;
467 scoped_feature_list.InitAndEnableFeature(features::kPdfIncrementalLoading);
468
469 NiceMock<MockTestClient> client;
470 InitializeEngineResult initialize_result = InitializeEngineWithoutLoading(
471 &client, FILE_PATH_LITERAL("linearized.pdf"));
472 ASSERT_TRUE(initialize_result.engine);
473 PDFiumEngine& engine = *initialize_result.engine;
474
475 // Load only some pages.
476 initialize_result.document_loader->SimulateLoadData(8192);
477
478 // Twice request a thumbnail for the second page, which is not loaded. The
479 // second call should crash.
480 base::MockCallback<SendThumbnailCallback> mock_callback;
481 engine.RequestThumbnail(/*page_index=*/1, /*device_pixel_ratio=*/1,
482 mock_callback.Get());
483 EXPECT_DCHECK_DEATH(engine.RequestThumbnail(
484 /*page_index=*/1, /*device_pixel_ratio=*/1, mock_callback.Get()));
485 }
486
487 class TabbingTestClient : public TestClient {
488 public:
489 TabbingTestClient() = default;
490 ~TabbingTestClient() override = default;
491 TabbingTestClient(const TabbingTestClient&) = delete;
492 TabbingTestClient& operator=(const TabbingTestClient&) = delete;
493
494 // Mock PDFEngine::Client methods.
495 MOCK_METHOD(void, DocumentFocusChanged, (bool), (override));
496 };
497
498 class PDFiumEngineTabbingTest : public PDFiumTestBase {
499 public:
500 PDFiumEngineTabbingTest() = default;
501 ~PDFiumEngineTabbingTest() override = default;
502 PDFiumEngineTabbingTest(const PDFiumEngineTabbingTest&) = delete;
503 PDFiumEngineTabbingTest& operator=(const PDFiumEngineTabbingTest&) = delete;
504
HandleTabEvent(PDFiumEngine * engine,uint32_t modifiers)505 bool HandleTabEvent(PDFiumEngine* engine, uint32_t modifiers) {
506 return engine->HandleTabEvent(modifiers);
507 }
508
GetFocusedElementType(PDFiumEngine * engine)509 PDFiumEngine::FocusElementType GetFocusedElementType(PDFiumEngine* engine) {
510 return engine->focus_item_type_;
511 }
512
GetLastFocusedPage(PDFiumEngine * engine)513 int GetLastFocusedPage(PDFiumEngine* engine) {
514 return engine->last_focused_page_;
515 }
516
GetLastFocusedElementType(PDFiumEngine * engine)517 PDFiumEngine::FocusElementType GetLastFocusedElementType(
518 PDFiumEngine* engine) {
519 return engine->last_focused_item_type_;
520 }
521
GetLastFocusedAnnotationIndex(PDFiumEngine * engine)522 int GetLastFocusedAnnotationIndex(PDFiumEngine* engine) {
523 return engine->last_focused_annot_index_;
524 }
525
IsInFormTextArea(PDFiumEngine * engine)526 bool IsInFormTextArea(PDFiumEngine* engine) {
527 return engine->in_form_text_area_;
528 }
529
GetSelectionSize(PDFiumEngine * engine)530 size_t GetSelectionSize(PDFiumEngine* engine) {
531 return engine->selection_.size();
532 }
533
GetLinkUnderCursor(PDFiumEngine * engine)534 const std::string& GetLinkUnderCursor(PDFiumEngine* engine) {
535 return engine->link_under_cursor_;
536 }
537
ScrollFocusedAnnotationIntoView(PDFiumEngine * engine)538 void ScrollFocusedAnnotationIntoView(PDFiumEngine* engine) {
539 engine->ScrollFocusedAnnotationIntoView();
540 }
541
542 protected:
543 base::test::TaskEnvironment task_environment_{
544 base::test::TaskEnvironment::TimeSource::MOCK_TIME};
545 };
546
TEST_F(PDFiumEngineTabbingTest,LinkUnderCursorTest)547 TEST_F(PDFiumEngineTabbingTest, LinkUnderCursorTest) {
548 /*
549 * Document structure
550 * Document
551 * ++ Page 1
552 * ++++ Widget annotation
553 * ++++ Widget annotation
554 * ++++ Highlight annotation
555 * ++++ Link annotation
556 */
557 // Enable feature flag.
558 base::test::ScopedFeatureList scoped_feature_list;
559 scoped_feature_list.InitAndEnableFeature(
560 chrome_pdf::features::kTabAcrossPDFAnnotations);
561
562 TestClient client;
563 std::unique_ptr<PDFiumEngine> engine =
564 InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
565 ASSERT_TRUE(engine);
566
567 // Initial value of link under cursor.
568 EXPECT_EQ("", GetLinkUnderCursor(engine.get()));
569
570 // Tab through non-link annotations and validate link under cursor.
571 for (int i = 0; i < 4; i++) {
572 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
573 EXPECT_EQ("", GetLinkUnderCursor(engine.get()));
574 }
575
576 // Tab to Link annotation.
577 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
578 EXPECT_EQ("https://www.google.com/", GetLinkUnderCursor(engine.get()));
579
580 // Tab to previous annotation.
581 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
582 EXPECT_EQ("", GetLinkUnderCursor(engine.get()));
583 }
584
TEST_F(PDFiumEngineTabbingTest,TabbingSupportedAnnots)585 TEST_F(PDFiumEngineTabbingTest, TabbingSupportedAnnots) {
586 /*
587 * Document structure
588 * Document
589 * ++ Page 1
590 * ++++ Widget annotation
591 * ++++ Widget annotation
592 * ++++ Highlight annotation
593 * ++++ Link annotation
594 */
595
596 // Enable feature flag.
597 base::test::ScopedFeatureList scoped_feature_list;
598 scoped_feature_list.InitAndEnableFeature(
599 chrome_pdf::features::kTabAcrossPDFAnnotations);
600
601 TestClient client;
602 std::unique_ptr<PDFiumEngine> engine =
603 InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
604 ASSERT_TRUE(engine);
605
606 ASSERT_EQ(1, engine->GetNumberOfPages());
607
608 ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
609 GetFocusedElementType(engine.get()));
610 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
611
612 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
613 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
614 GetFocusedElementType(engine.get()));
615
616 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
617 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
618 GetFocusedElementType(engine.get()));
619 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
620
621 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
622 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
623 GetFocusedElementType(engine.get()));
624 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
625
626 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
627 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
628 GetFocusedElementType(engine.get()));
629 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
630
631 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
632 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
633 GetFocusedElementType(engine.get()));
634 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
635
636 ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
637 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
638 GetFocusedElementType(engine.get()));
639 }
640
TEST_F(PDFiumEngineTabbingTest,TabbingForwardTest)641 TEST_F(PDFiumEngineTabbingTest, TabbingForwardTest) {
642 /*
643 * Document structure
644 * Document
645 * ++ Page 1
646 * ++++ Annotation
647 * ++++ Annotation
648 * ++ Page 2
649 * ++++ Annotation
650 */
651 TabbingTestClient client;
652 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
653 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
654 ASSERT_TRUE(engine);
655
656 ASSERT_EQ(2, engine->GetNumberOfPages());
657
658 static constexpr bool kExpectedFocusState[] = {true, false};
659 {
660 InSequence sequence;
661 for (auto focused : kExpectedFocusState)
662 EXPECT_CALL(client, DocumentFocusChanged(focused));
663 }
664
665 ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
666 GetFocusedElementType(engine.get()));
667 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
668
669 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
670 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
671 GetFocusedElementType(engine.get()));
672
673 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
674 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
675 GetFocusedElementType(engine.get()));
676 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
677
678 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
679 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
680 GetFocusedElementType(engine.get()));
681 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
682
683 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
684 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
685 GetFocusedElementType(engine.get()));
686 EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
687
688 ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
689 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
690 GetFocusedElementType(engine.get()));
691 }
692
TEST_F(PDFiumEngineTabbingTest,TabbingBackwardTest)693 TEST_F(PDFiumEngineTabbingTest, TabbingBackwardTest) {
694 /*
695 * Document structure
696 * Document
697 * ++ Page 1
698 * ++++ Annotation
699 * ++++ Annotation
700 * ++ Page 2
701 * ++++ Annotation
702 */
703 TabbingTestClient client;
704 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
705 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
706 ASSERT_TRUE(engine);
707
708 ASSERT_EQ(2, engine->GetNumberOfPages());
709
710 static constexpr bool kExpectedFocusState[] = {true, false};
711 {
712 InSequence sequence;
713 for (auto focused : kExpectedFocusState)
714 EXPECT_CALL(client, DocumentFocusChanged(focused));
715 }
716
717 ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
718 GetFocusedElementType(engine.get()));
719 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
720
721 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
722 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
723 GetFocusedElementType(engine.get()));
724 EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
725
726 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
727 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
728 GetFocusedElementType(engine.get()));
729 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
730
731 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
732 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
733 GetFocusedElementType(engine.get()));
734 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
735
736 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
737 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
738 GetFocusedElementType(engine.get()));
739
740 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
741 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
742 GetFocusedElementType(engine.get()));
743 }
744
TEST_F(PDFiumEngineTabbingTest,TabbingWithModifiers)745 TEST_F(PDFiumEngineTabbingTest, TabbingWithModifiers) {
746 /*
747 * Document structure
748 * Document
749 * ++ Page 1
750 * ++++ Annotation
751 * ++++ Annotation
752 * ++ Page 2
753 * ++++ Annotation
754 */
755 TestClient client;
756 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
757 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
758 ASSERT_TRUE(engine);
759
760 ASSERT_EQ(2, engine->GetNumberOfPages());
761
762 ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
763 GetFocusedElementType(engine.get()));
764 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
765
766 // Tabbing with ctrl modifier.
767 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierControlKey));
768 // Tabbing with alt modifier.
769 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierAltKey));
770
771 // Tab to bring document into focus.
772 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
773 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
774 GetFocusedElementType(engine.get()));
775
776 // Tabbing with ctrl modifier.
777 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierControlKey));
778 // Tabbing with alt modifier.
779 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierAltKey));
780
781 // Tab to bring first page into focus.
782 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
783 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
784 GetFocusedElementType(engine.get()));
785
786 // Tabbing with ctrl modifier.
787 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierControlKey));
788 // Tabbing with alt modifier.
789 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierAltKey));
790 }
791
TEST_F(PDFiumEngineTabbingTest,NoFocusableItemTabbingTest)792 TEST_F(PDFiumEngineTabbingTest, NoFocusableItemTabbingTest) {
793 /*
794 * Document structure
795 * Document
796 * ++ Page 1
797 * ++ Page 2
798 */
799 TabbingTestClient client;
800 std::unique_ptr<PDFiumEngine> engine =
801 InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
802 ASSERT_TRUE(engine);
803
804 ASSERT_EQ(2, engine->GetNumberOfPages());
805
806 static constexpr bool kExpectedFocusState[] = {true, false, true, false};
807 {
808 InSequence sequence;
809 for (auto focused : kExpectedFocusState)
810 EXPECT_CALL(client, DocumentFocusChanged(focused));
811 }
812
813 ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
814 GetFocusedElementType(engine.get()));
815 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
816
817 // Tabbing forward.
818 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
819 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
820 GetFocusedElementType(engine.get()));
821
822 ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
823 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
824 GetFocusedElementType(engine.get()));
825
826 // Tabbing backward.
827 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
828 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
829 GetFocusedElementType(engine.get()));
830
831 ASSERT_FALSE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
832 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
833 GetFocusedElementType(engine.get()));
834 }
835
TEST_F(PDFiumEngineTabbingTest,RestoringDocumentFocusTest)836 TEST_F(PDFiumEngineTabbingTest, RestoringDocumentFocusTest) {
837 /*
838 * Document structure
839 * Document
840 * ++ Page 1
841 * ++++ Annotation
842 * ++++ Annotation
843 * ++ Page 2
844 * ++++ Annotation
845 */
846 TabbingTestClient client;
847 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
848 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
849 ASSERT_TRUE(engine);
850
851 ASSERT_EQ(2, engine->GetNumberOfPages());
852
853 static constexpr bool kExpectedFocusState[] = {true, false, true};
854 {
855 InSequence sequence;
856 for (auto focused : kExpectedFocusState)
857 EXPECT_CALL(client, DocumentFocusChanged(focused));
858 }
859
860 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
861 GetFocusedElementType(engine.get()));
862 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
863
864 // Tabbing to bring the document into focus.
865 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
866 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
867 GetFocusedElementType(engine.get()));
868
869 engine->UpdateFocus(/*has_focus=*/false);
870 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
871 GetFocusedElementType(engine.get()));
872 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
873 GetLastFocusedElementType(engine.get()));
874 EXPECT_EQ(-1, GetLastFocusedAnnotationIndex(engine.get()));
875
876 engine->UpdateFocus(/*has_focus=*/true);
877 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
878 GetFocusedElementType(engine.get()));
879 }
880
TEST_F(PDFiumEngineTabbingTest,RestoringAnnotFocusTest)881 TEST_F(PDFiumEngineTabbingTest, RestoringAnnotFocusTest) {
882 /*
883 * Document structure
884 * Document
885 * ++ Page 1
886 * ++++ Annotation
887 * ++++ Annotation
888 * ++ Page 2
889 * ++++ Annotation
890 */
891 TabbingTestClient client;
892 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
893 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
894 ASSERT_TRUE(engine);
895
896 ASSERT_EQ(2, engine->GetNumberOfPages());
897
898 static constexpr bool kExpectedFocusState[] = {true, false};
899 {
900 InSequence sequence;
901 for (auto focused : kExpectedFocusState)
902 EXPECT_CALL(client, DocumentFocusChanged(focused));
903 }
904
905 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
906 GetFocusedElementType(engine.get()));
907 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
908
909 // Tabbing to bring last annotation of page 0 into focus.
910 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
911 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
912 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
913
914 engine->UpdateFocus(/*has_focus=*/false);
915 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
916 GetLastFocusedElementType(engine.get()));
917 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
918 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
919 GetFocusedElementType(engine.get()));
920 EXPECT_EQ(0, GetLastFocusedAnnotationIndex(engine.get()));
921
922 engine->UpdateFocus(/*has_focus=*/true);
923 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
924 GetFocusedElementType(engine.get()));
925 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
926
927 // Tabbing now should bring the second page's annotation to focus.
928 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
929 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
930 GetFocusedElementType(engine.get()));
931 EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
932 }
933
TEST_F(PDFiumEngineTabbingTest,VerifyFormFieldStatesOnTabbing)934 TEST_F(PDFiumEngineTabbingTest, VerifyFormFieldStatesOnTabbing) {
935 /*
936 * Document structure
937 * Document
938 * ++ Page 1
939 * ++++ Annotation (Text Field)
940 * ++++ Annotation (Radio Button)
941 */
942 TestClient client;
943 std::unique_ptr<PDFiumEngine> engine =
944 InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
945 ASSERT_TRUE(engine);
946 ASSERT_EQ(1, engine->GetNumberOfPages());
947
948 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
949 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
950 GetFocusedElementType(engine.get()));
951
952 // Bring focus to the text field.
953 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
954 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
955 GetFocusedElementType(engine.get()));
956 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
957 EXPECT_TRUE(IsInFormTextArea(engine.get()));
958 EXPECT_TRUE(engine->CanEditText());
959
960 // Bring focus to the button.
961 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
962 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
963 GetFocusedElementType(engine.get()));
964 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
965 EXPECT_FALSE(IsInFormTextArea(engine.get()));
966 EXPECT_FALSE(engine->CanEditText());
967 }
968
TEST_F(PDFiumEngineTabbingTest,ClearSelectionOnFocusInFormTextArea)969 TEST_F(PDFiumEngineTabbingTest, ClearSelectionOnFocusInFormTextArea) {
970 TestClient client;
971 std::unique_ptr<PDFiumEngine> engine =
972 InitializeEngine(&client, FILE_PATH_LITERAL("form_text_fields.pdf"));
973 ASSERT_TRUE(engine);
974 ASSERT_EQ(1, engine->GetNumberOfPages());
975
976 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
977 GetFocusedElementType(engine.get()));
978 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
979
980 // Select all text.
981 engine->SelectAll();
982 EXPECT_EQ(1u, GetSelectionSize(engine.get()));
983
984 // Tab to bring focus to a form text area annotation.
985 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
986 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
987 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
988 GetFocusedElementType(engine.get()));
989 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
990 EXPECT_EQ(0u, GetSelectionSize(engine.get()));
991 }
992
TEST_F(PDFiumEngineTabbingTest,RetainSelectionOnFocusNotInFormTextArea)993 TEST_F(PDFiumEngineTabbingTest, RetainSelectionOnFocusNotInFormTextArea) {
994 TestClient client;
995 std::unique_ptr<PDFiumEngine> engine =
996 InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
997 ASSERT_TRUE(engine);
998 ASSERT_EQ(1, engine->GetNumberOfPages());
999
1000 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
1001 GetFocusedElementType(engine.get()));
1002 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
1003
1004 // Select all text.
1005 engine->SelectAll();
1006 EXPECT_EQ(1u, GetSelectionSize(engine.get()));
1007
1008 // Tab to bring focus to a non form text area annotation (Button).
1009 ASSERT_TRUE(HandleTabEvent(engine.get(), kInputEventModifierShiftKey));
1010 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
1011 GetFocusedElementType(engine.get()));
1012 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
1013 EXPECT_EQ(1u, GetSelectionSize(engine.get()));
1014 }
1015
1016 class ScrollingTestClient : public TestClient {
1017 public:
1018 ScrollingTestClient() = default;
1019 ~ScrollingTestClient() override = default;
1020 ScrollingTestClient(const ScrollingTestClient&) = delete;
1021 ScrollingTestClient& operator=(const ScrollingTestClient&) = delete;
1022
1023 // Mock PDFEngine::Client methods.
1024 MOCK_METHOD(void, ScrollToX, (int), (override));
1025 MOCK_METHOD(void, ScrollToY, (int, bool), (override));
1026 };
1027
TEST_F(PDFiumEngineTabbingTest,MaintainViewportWhenFocusIsUpdated)1028 TEST_F(PDFiumEngineTabbingTest, MaintainViewportWhenFocusIsUpdated) {
1029 StrictMock<ScrollingTestClient> client;
1030 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
1031 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
1032 ASSERT_TRUE(engine);
1033 ASSERT_EQ(2, engine->GetNumberOfPages());
1034 engine->PluginSizeUpdated(gfx::Size(60, 40));
1035
1036 {
1037 InSequence sequence;
1038 static constexpr gfx::Point kScrollValue = {510, 478};
1039 EXPECT_CALL(client, ScrollToY(kScrollValue.y(), false))
1040 .WillOnce(Invoke(
1041 [&engine]() { engine->ScrolledToYPosition(kScrollValue.y()); }));
1042 EXPECT_CALL(client, ScrollToX(kScrollValue.x()))
1043 .WillOnce(Invoke(
1044 [&engine]() { engine->ScrolledToXPosition(kScrollValue.x()); }));
1045 }
1046
1047 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
1048 GetFocusedElementType(engine.get()));
1049 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
1050
1051 // Tabbing to bring the document into focus.
1052 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
1053 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
1054 GetFocusedElementType(engine.get()));
1055
1056 // Tab to an annotation.
1057 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
1058 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
1059 GetFocusedElementType(engine.get()));
1060
1061 // Scroll focused annotation out of viewport.
1062 static constexpr gfx::Point kScrollPosition = {242, 746};
1063 engine->ScrolledToXPosition(kScrollPosition.x());
1064 engine->ScrolledToYPosition(kScrollPosition.y());
1065
1066 engine->UpdateFocus(/*has_focus=*/false);
1067 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
1068 GetLastFocusedElementType(engine.get()));
1069 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
1070 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
1071 GetFocusedElementType(engine.get()));
1072 EXPECT_EQ(1, GetLastFocusedAnnotationIndex(engine.get()));
1073
1074 // Restore focus, we shouldn't have any calls to scroll viewport.
1075 engine->UpdateFocus(/*has_focus=*/true);
1076 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
1077 GetFocusedElementType(engine.get()));
1078 EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
1079 }
1080
TEST_F(PDFiumEngineTabbingTest,ScrollFocusedAnnotationIntoView)1081 TEST_F(PDFiumEngineTabbingTest, ScrollFocusedAnnotationIntoView) {
1082 StrictMock<ScrollingTestClient> client;
1083 std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
1084 &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
1085 ASSERT_TRUE(engine);
1086 ASSERT_EQ(2, engine->GetNumberOfPages());
1087 engine->PluginSizeUpdated(gfx::Size(60, 40));
1088
1089 {
1090 InSequence sequence;
1091 static constexpr gfx::Point kScrollValues[] = {{510, 478}, {510, 478}};
1092
1093 for (const auto& scroll_value : kScrollValues) {
1094 EXPECT_CALL(client, ScrollToY(scroll_value.y(), false))
1095 .WillOnce(Invoke([&engine, &scroll_value]() {
1096 engine->ScrolledToYPosition(scroll_value.y());
1097 }));
1098 EXPECT_CALL(client, ScrollToX(scroll_value.x()))
1099 .WillOnce(Invoke([&engine, &scroll_value]() {
1100 engine->ScrolledToXPosition(scroll_value.x());
1101 }));
1102 }
1103 }
1104
1105 EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
1106 GetFocusedElementType(engine.get()));
1107 EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
1108
1109 // Tabbing to bring the document into focus.
1110 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
1111 EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
1112 GetFocusedElementType(engine.get()));
1113
1114 // Tab to an annotation.
1115 ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
1116 EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
1117 GetFocusedElementType(engine.get()));
1118
1119 // Scroll focused annotation out of viewport.
1120 static constexpr gfx::Point kScrollPosition = {242, 746};
1121 engine->ScrolledToXPosition(kScrollPosition.x());
1122 engine->ScrolledToYPosition(kScrollPosition.y());
1123
1124 // Scroll the focused annotation into view.
1125 ScrollFocusedAnnotationIntoView(engine.get());
1126 }
1127
1128 } // namespace chrome_pdf
1129