Add tests for listbox form scrolling

Currently, listbox forms do not scroll to the top selected option when
the form is out of focus. A test is added with the current incorrect
functionality, and will be changed in CL [1].

Another test is added to make sure the listbox does not overscroll when
the top index ("TI") is specified to the last element.

[1] https://pdfium-review.googlesource.com/c/pdfium/+/59190

Bug: pdfium:1377
Change-Id: I67b8bb434d2b8cdcd9a66eca44220a4ad9f86d2b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/59890
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index bfcbe8c..33353aa 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -368,6 +368,10 @@
     // - "Listbox_SingleSelect" - Ff: 0, 3 options with pair values.
     // - "Listbox_MultiSelect" - Ff: 2097152, 26 options with single values.
     // - "Listbox_ReadOnly" - Ff: 1, 3 options with single values.
+    // - "Listbox_MultiSelectMultipleSelected" - Ff: 2097152, 5 options with
+    // single values.
+    // - "Listbox_SingleSelectLastSelected" - Ff: 0, 10 options with single
+    // values.
     return "listbox_form.pdf";
   }
 
@@ -382,6 +386,17 @@
               GetFormTypeAtPoint(MultiSelectFirstVisibleOption()));
     EXPECT_EQ(GetFormType(),
               GetFormTypeAtPoint(MultiSelectSecondVisibleOption()));
+    EXPECT_EQ(
+        GetFormType(),
+        GetFormTypeAtPoint(MultiSelectMultipleSelectedFirstVisibleOption()));
+    EXPECT_EQ(
+        GetFormType(),
+        GetFormTypeAtPoint(MultiSelectMultipleSelectedSecondVisibleOption()));
+    EXPECT_EQ(GetFormType(),
+              GetFormTypeAtPoint(SingleSelectLastSelectedFirstVisibleOption()));
+    EXPECT_EQ(
+        GetFormType(),
+        GetFormTypeAtPoint(SingleSelectLastSelectedSecondVisibleOption()));
   }
 
   void ClickOnSingleSelectFormOption(int item_index) {
@@ -408,6 +423,30 @@
     }
   }
 
+  void ClickOnMultiSelectMultipleSelectedFormOption(int item_index) {
+    // Only two indices are visible so can only click on those
+    // without scrolling.
+    ASSERT(item_index >= 0);
+    ASSERT(item_index < 2);
+    if (item_index == 0) {
+      ClickOnFormFieldAtPoint(MultiSelectMultipleSelectedFirstVisibleOption());
+    } else {
+      ClickOnFormFieldAtPoint(MultiSelectMultipleSelectedSecondVisibleOption());
+    }
+  }
+
+  void ClickOnSingleSelectLastSelectedFormOption(int item_index) {
+    // Only two indices are visible so can only click on those
+    // without scrolling.
+    ASSERT(item_index >= 0);
+    ASSERT(item_index < 2);
+    if (item_index == 0) {
+      ClickOnFormFieldAtPoint(SingleSelectLastSelectedFirstVisibleOption());
+    } else {
+      ClickOnFormFieldAtPoint(SingleSelectLastSelectedSecondVisibleOption());
+    }
+  }
+
   void FocusOnSingleSelectForm() {
     FocusOnPoint(SingleSelectFirstVisibleOption());
   }
@@ -416,6 +455,14 @@
     FocusOnPoint(MultiSelectFirstVisibleOption());
   }
 
+  void FocusOnMultiSelectMultipleSelectedForm() {
+    FocusOnPoint(MultiSelectMultipleSelectedFirstVisibleOption());
+  }
+
+  void FocusOnSingleSelectLastSelectedForm() {
+    FocusOnPoint(SingleSelectLastSelectedFirstVisibleOption());
+  }
+
   void FocusOnPoint(const CFX_PointF& point) {
     EXPECT_EQ(true, FORM_OnFocus(form_handle(), page(), 0, point.x, point.y));
   }
@@ -440,12 +487,40 @@
     return point;
   }
 
+  const CFX_PointF& MultiSelectMultipleSelectedFirstVisibleOption() const {
+    static const CFX_PointF point(
+        kFormBeginX, kMultiFormMultipleSelectedYFirstVisibleOption);
+    return point;
+  }
+
+  const CFX_PointF& MultiSelectMultipleSelectedSecondVisibleOption() const {
+    static const CFX_PointF point(
+        kFormBeginX, kMultiFormMultipleSelectedYSecondVisibleOption);
+    return point;
+  }
+
+  const CFX_PointF& SingleSelectLastSelectedFirstVisibleOption() const {
+    static const CFX_PointF point(kFormBeginX,
+                                  kSingleFormLastSelectedYFirstVisibleOption);
+    return point;
+  }
+
+  const CFX_PointF& SingleSelectLastSelectedSecondVisibleOption() const {
+    static const CFX_PointF point(kFormBeginX,
+                                  kSingleFormLastSelectedYSecondVisibleOption);
+    return point;
+  }
+
  private:
   static constexpr float kFormBeginX = 102.0;
   static constexpr float kSingleFormYFirstVisibleOption = 371.0;
   static constexpr float kSingleFormYSecondVisibleOption = 358.0;
   static constexpr float kMultiFormYFirstVisibleOption = 423.0;
   static constexpr float kMultiFormYSecondVisibleOption = 408.0;
+  static constexpr float kMultiFormMultipleSelectedYFirstVisibleOption = 223.0;
+  static constexpr float kMultiFormMultipleSelectedYSecondVisibleOption = 208.0;
+  static constexpr float kSingleFormLastSelectedYFirstVisibleOption = 123.0;
+  static constexpr float kSingleFormLastSelectedYSecondVisibleOption = 108.0;
 };
 
 TEST_F(FPDFFormFillEmbedderTest, FirstTest) {
@@ -2126,9 +2201,13 @@
     CheckIsIndexSelected(i, expected);
   }
 
-  ClickOnMultiSelectFormOption(0);
+  // TODO(bug_1377): Behavior should be changed to the one described below.
+  // Multiselect field set to 'Cherry' (index 2), which is index 1 among the
+  // visible form options because the listbox is scrolled down to have 'Banana'
+  // (index 1) at the top.
+  ClickOnMultiSelectFormOption(1);
   for (int i = 0; i < 26; i++) {
-    bool expected = i == 0;
+    bool expected = i == 1;
     CheckIsIndexSelected(i, expected);
   }
 }
@@ -2263,6 +2342,7 @@
 
   // Check that above actions are interchangeable with click actions, should be
   // able to use a combination of both.
+  // TODO(bug_1377): Change to click on form option 0 instead of form option 1
   ClickOnMultiSelectFormOption(1);
   for (int i = 0; i < 26; i++) {
     bool expected = i == 1;
@@ -2271,6 +2351,51 @@
   CheckFocusedFieldText(L"Banana");
 }
 
+TEST_F(FPDFFormFillListBoxFormEmbedderTest, CheckIfMultipleSelected) {
+  // Multiselect field set to 'Gamma' (index 2) and 'Epsilon' (index 4) upon
+  // opening.
+  FocusOnMultiSelectMultipleSelectedForm();
+  for (int i = 0; i < 5; i++) {
+    // TODO(bug_1377): Should be selected at index 2 and index 4.
+    bool expected = false;
+    CheckIsIndexSelected(i, expected);
+  }
+}
+
+TEST_F(FPDFFormFillListBoxFormEmbedderTest,
+       CheckIfVerticalScrollIsAtFirstSelected) {
+  // Multiselect field set to 'Gamma' (index 2) and 'Epsilon' (index 4) upon
+  // opening.
+
+  // TODO(bug_1377): Behavior should be changed to the one described below.
+  // The top visible option is 'Gamma' (index 2), so the first selection should
+  // not change. The second selection, 'Epsilon,' should be deselected.
+  ClickOnMultiSelectMultipleSelectedFormOption(0);
+  for (int i = 0; i < 5; i++) {
+    bool expected = i == 0;
+    CheckIsIndexSelected(i, expected);
+  }
+}
+
+TEST_F(FPDFFormFillListBoxFormEmbedderTest, CheckForNoOverscroll) {
+  // Only the last option in the list, 'Saskatchewan', is selected.
+  FocusOnSingleSelectLastSelectedForm();
+  for (int i = 0; i < 10; i++) {
+    bool expected = i == 9;
+    CheckIsIndexSelected(i, expected);
+  }
+
+  // Even though the top index is specified to be at 'Saskatchewan' (index 9),
+  // the top visible option will be the one above it, 'Quebec' (index 8), to
+  // prevent overscrolling. Therefore, clicking on the first visible option of
+  // the list should select 'Quebec' instead of 'Saskatchewan.'
+  ClickOnSingleSelectLastSelectedFormOption(0);
+  for (int i = 0; i < 10; i++) {
+    bool expected = i == 8;
+    CheckIsIndexSelected(i, expected);
+  }
+}
+
 TEST_F(FPDFFormFillTextFormEmbedderTest, ReplaceSelection) {
   ScopedFPDFWideString text_to_insert = GetFPDFWideString(L"XYZ");
   ClickOnFormFieldAtPoint(RegularFormBegin());
diff --git a/testing/resources/listbox_form.in b/testing/resources/listbox_form.in
index 03acd56..334d068 100644
--- a/testing/resources/listbox_form.in
+++ b/testing/resources/listbox_form.in
@@ -3,7 +3,7 @@
 <<
   /Type /Catalog
   /Pages 2 0 R
-  /AcroForm << /Fields [ 8 0 R 9 0 R 10 0 R ] /DR 4 0 R >>
+  /AcroForm << /Fields [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /DR 4 0 R >>
 >>
 endobj
 {{object 2 0}}
@@ -16,7 +16,7 @@
   /Resources 4 0 R
   /MediaBox [ 0 0 300 600 ]
   /Contents 7 0 R
-  /Annots [ 8 0 R 9 0 R 10 0 R ]
+  /Annots [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ]
 >>
 endobj
 {{object 4 0}}
@@ -83,6 +83,35 @@
   /Opt [(Dog) (Elephant) (Frog)]
 >>
 endobj
+{{object 11 0}}
+<<
+  /Type /Annot
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleSelected)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [ 100 200 200 230 ]
+  /Subtype /Widget
+  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
+  /V [(Epsilon) (Gamma)]
+>>
+endobj
+{{object 12 0}}
+<<
+  /Type /Annot
+  /FT /Ch
+  /Ff 0
+  /T (Listbox_SingleSelectLastSelected)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [ 100 100 200 130 ]
+  /Subtype /Widget
+  /Opt [(Alberta) (British Columbia) (Manitoba) (New Brunswick)
+        (Newfoundland and Labrador) (Nova Scotia) (Ontario)
+        (Prince Edward Island) (Quebec) (Saskatchewan) ]
+  /V (Saskatchewan)
+  /TI 9
+>>
+endobj
 {{xref}}
 {{trailer}}
 {{startxref}}
diff --git a/testing/resources/listbox_form.pdf b/testing/resources/listbox_form.pdf
index 7c244d1..51a7fb7 100644
--- a/testing/resources/listbox_form.pdf
+++ b/testing/resources/listbox_form.pdf
@@ -4,7 +4,7 @@
 <<
   /Type /Catalog
   /Pages 2 0 R
-  /AcroForm << /Fields [ 8 0 R 9 0 R 10 0 R ] /DR 4 0 R >>
+  /AcroForm << /Fields [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /DR 4 0 R >>
 >>
 endobj
 2 0 obj
@@ -17,7 +17,7 @@
   /Resources 4 0 R
   /MediaBox [ 0 0 300 600 ]
   /Contents 7 0 R
-  /Annots [ 8 0 R 9 0 R 10 0 R ]
+  /Annots [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ]
 >>
 endobj
 4 0 obj
@@ -84,23 +84,54 @@
   /Opt [(Dog) (Elephant) (Frog)]
 >>
 endobj
+11 0 obj
+<<
+  /Type /Annot
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleSelected)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [ 100 200 200 230 ]
+  /Subtype /Widget
+  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
+  /V [(Epsilon) (Gamma)]
+>>
+endobj
+12 0 obj
+<<
+  /Type /Annot
+  /FT /Ch
+  /Ff 0
+  /T (Listbox_SingleSelectLastSelected)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [ 100 100 200 130 ]
+  /Subtype /Widget
+  /Opt [(Alberta) (British Columbia) (Manitoba) (New Brunswick)
+        (Newfoundland and Labrador) (Nova Scotia) (Ontario)
+        (Prince Edward Island) (Quebec) (Saskatchewan) ]
+  /V (Saskatchewan)
+  /TI 9
+>>
+endobj
 xref
-0 11
+0 13
 0000000000 65535 f 
 0000000015 00000 n 
-0000000127 00000 n 
-0000000186 00000 n 
-0000000335 00000 n 
-0000000368 00000 n 
-0000000399 00000 n 
-0000000475 00000 n 
-0000000575 00000 n 
-0000000782 00000 n 
-0000001252 00000 n 
+0000000141 00000 n 
+0000000200 00000 n 
+0000000363 00000 n 
+0000000396 00000 n 
+0000000427 00000 n 
+0000000503 00000 n 
+0000000603 00000 n 
+0000000810 00000 n 
+0000001280 00000 n 
+0000001466 00000 n 
+0000001719 00000 n 
 trailer <<
   /Root 1 0 R
-  /Size 11
+  /Size 13
 >>
 startxref
-1438
+2097
 %%EOF