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