Add FORM_OnMouseWheel().

PDFium has scroll wheel support internally in many form widget
implementations, but none of it is hooked up via a public API.

Add an experimental public API and hook it up to pdfium_test. In
pdfium_test, add a way to specify wheel events using the following
syntax:

mousewheel,pointer_x,pointer_y,delta_x,delta_y[,modifiers]

Use the new event to test scrolling a list widget in a pixel test.

Bug: pdfium:1452
Change-Id: I87f40243399aa79b50177e8b833e4504becc8039
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/68470
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_formfill.cpp b/fpdfsdk/fpdf_formfill.cpp
index 96e4c20..ac09b2e 100644
--- a/fpdfsdk/fpdf_formfill.cpp
+++ b/fpdfsdk/fpdf_formfill.cpp
@@ -379,6 +379,20 @@
   return pPageView->OnMouseMove(CFX_PointF(page_x, page_y), modifier);
 }
 
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FORM_OnMouseWheel(FPDF_FORMHANDLE hHandle,
+                  FPDF_PAGE page,
+                  int modifier,
+                  const FS_POINTF* page_coord,
+                  int delta_x,
+                  int delta_y) {
+  CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page);
+  if (!pPageView || !page_coord)
+    return false;
+  return pPageView->OnMouseWheel(delta_x, delta_y,
+                                 CFXPointFFromFSPointF(*page_coord), modifier);
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnFocus(FPDF_FORMHANDLE hHandle,
                                                  FPDF_PAGE page,
                                                  int modifier,
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index b7353aa..38f302d 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -258,6 +258,7 @@
     CHK(FORM_OnLButtonDown);
     CHK(FORM_OnLButtonUp);
     CHK(FORM_OnMouseMove);
+    CHK(FORM_OnMouseWheel);
     CHK(FORM_OnRButtonDown);
     CHK(FORM_OnRButtonUp);
     CHK(FORM_Redo);
diff --git a/public/fpdf_formfill.h b/public/fpdf_formfill.h
index 84537e4..1f656b0 100644
--- a/public/fpdf_formfill.h
+++ b/public/fpdf_formfill.h
@@ -1272,6 +1272,39 @@
                                                      double page_y);
 
 /*
+ * Experimental API
+ * Function: FORM_OnMouseWheel
+ *       Call this member function when the user scrolls the mouse wheel.
+ * Parameters:
+ *       hHandle     -   Handle to the form fill module, as returned by
+ *                       FPDFDOC_InitFormFillEnvironment().
+ *       page        -   Handle to the page, as returned by FPDF_LoadPage().
+ *       modifier    -   Indicates whether various virtual keys are down.
+ *       page_coord  -   Specifies the coordinates of the cursor in PDF user
+ *                       space.
+ *       delta_x     -   Specifies the amount of wheel movement on the x-axis,
+ *                       in units of platform-agnostic wheel deltas. Negative
+ *                       values mean left.
+ *       delta_y     -   Specifies the amount of wheel movement on the y-axis,
+ *                       in units of platform-agnostic wheel deltas. Negative
+ *                       values mean down.
+ * Return Value:
+ *       True indicates success; otherwise false.
+ * Comments:
+ *       For |delta_x| and |delta_y|, the caller must normalize
+ *       platform-specific wheel deltas. e.g. On Windows, a delta value of 240
+ *       for a WM_MOUSEWHEEL event normalizes to 2, since Windows defines
+ *       WHEEL_DELTA as 120.
+ */
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnMouseWheel(
+    FPDF_FORMHANDLE hHandle,
+    FPDF_PAGE page,
+    int modifier,
+    const FS_POINTF* page_coord,
+    int delta_x,
+    int delta_y);
+
+/*
  * Function: FORM_OnFocus
  *       This function focuses the form annotation at a given point. If the
  *       annotation at the point already has focus, nothing happens. If there
@@ -1299,7 +1332,7 @@
  *       Call this member function when the user presses the left
  *       mouse button.
  * Parameters:
- *       hHandle     -   Handle to the form fill module. as returned by
+ *       hHandle     -   Handle to the form fill module, as returned by
  *                       FPDFDOC_InitFormFillEnvironment().
  *       page        -   Handle to the page, as returned by FPDF_LoadPage().
  *       modifier    -   Indicates whether various virtual keys are down.
@@ -1335,7 +1368,7 @@
  * Parameters:
  *       hHandle     -   Handle to the form fill module, as returned by
  *                       FPDFDOC_InitFormFillEnvironment().
- *       page        -   Handle to the page. as returned by FPDF_LoadPage().
+ *       page        -   Handle to the page, as returned by FPDF_LoadPage().
  *       modifier    -   Indicates whether various virtual keys are down.
  *       page_x      -   Specifies the x-coordinate of the cursor in device.
  *       page_y      -   Specifies the y-coordinate of the cursor in device.
@@ -1536,7 +1569,7 @@
  * Function: FORM_Undo
  *       Make the current focussed widget perform an undo operation.
  * Parameters:
- *       hHandle     -   Handle to the form fill module. as returned by
+ *       hHandle     -   Handle to the form fill module, as returned by
  *                       FPDFDOC_InitFormFillEnvironment().
  *       page        -   Handle to the page, as returned by FPDF_LoadPage().
  * Return Value:
diff --git a/samples/pdfium_test_event_helper.cc b/samples/pdfium_test_event_helper.cc
index 2e1abe9..f45063d 100644
--- a/samples/pdfium_test_event_helper.cc
+++ b/samples/pdfium_test_event_helper.cc
@@ -56,7 +56,7 @@
 void SendMouseDownEvent(FPDF_FORMHANDLE form,
                         FPDF_PAGE page,
                         const std::vector<std::string>& tokens) {
-  if (tokens.size() != 4 && tokens.size() != 5) {
+  if (tokens.size() < 4 && tokens.size() > 5) {
     fprintf(stderr, "mousedown: bad args\n");
     return;
   }
@@ -76,7 +76,7 @@
 void SendMouseUpEvent(FPDF_FORMHANDLE form,
                       FPDF_PAGE page,
                       const std::vector<std::string>& tokens) {
-  if (tokens.size() != 4 && tokens.size() != 5) {
+  if (tokens.size() < 4 && tokens.size() > 5) {
     fprintf(stderr, "mouseup: bad args\n");
     return;
   }
@@ -95,7 +95,7 @@
 void SendMouseDoubleClickEvent(FPDF_FORMHANDLE form,
                                FPDF_PAGE page,
                                const std::vector<std::string>& tokens) {
-  if (tokens.size() != 4 && tokens.size() != 5) {
+  if (tokens.size() < 4 && tokens.size() > 5) {
     fprintf(stderr, "mousedoubleclick: bad args\n");
     return;
   }
@@ -123,6 +123,21 @@
   FORM_OnMouseMove(form, page, 0, x, y);
 }
 
+void SendMouseWheelEvent(FPDF_FORMHANDLE form,
+                         FPDF_PAGE page,
+                         const std::vector<std::string>& tokens) {
+  if (tokens.size() < 5 && tokens.size() > 6) {
+    fprintf(stderr, "mousewheel: bad args\n");
+    return;
+  }
+
+  const FS_POINTF point = {atoi(tokens[1].c_str()), atoi(tokens[2].c_str())};
+  int delta_x = atoi(tokens[3].c_str());
+  int delta_y = atoi(tokens[4].c_str());
+  int modifiers = tokens.size() >= 6 ? GetModifiers(tokens[5]) : 0;
+  FORM_OnMouseWheel(form, page, modifiers, &point, delta_x, delta_y);
+}
+
 void SendFocusEvent(FPDF_FORMHANDLE form,
                     FPDF_PAGE page,
                     const std::vector<std::string>& tokens) {
@@ -135,6 +150,7 @@
   int y = atoi(tokens[2].c_str());
   FORM_OnFocus(form, page, 0, x, y);
 }
+
 }  // namespace
 
 void SendPageEvents(FPDF_FORMHANDLE form,
@@ -158,6 +174,8 @@
       SendMouseDoubleClickEvent(form, page, tokens);
     } else if (tokens[0] == "mousemove") {
       SendMouseMoveEvent(form, page, tokens);
+    } else if (tokens[0] == "mousewheel") {
+      SendMouseWheelEvent(form, page, tokens);
     } else if (tokens[0] == "focus") {
       SendFocusEvent(form, page, tokens);
     } else {
diff --git a/testing/resources/pixel/scrollable_widgets1.evt b/testing/resources/pixel/scrollable_widgets1.evt
new file mode 100644
index 0000000..c595cd7
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1.evt
@@ -0,0 +1,29 @@
+# Must move the mouse and click to give widget focus.
+mousemove,150,415
+mousedown,left,150,415
+# Scroll all the way down.
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
diff --git a/testing/resources/pixel/scrollable_widgets1.in b/testing/resources/pixel/scrollable_widgets1.in
new file mode 100644
index 0000000..9453b7c
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1.in
@@ -0,0 +1,52 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R]
+    /DR <<
+      /Font <<
+        /F1 4 0 R
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 300 600]
+  /Annots [5 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelect)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 400 200 430]
+  /Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
+        (Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
+        (Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
+        (Voavanga) (Wolfberry) (Xigua) (Yangmei) (Zucchini)]
+  /V (Banana)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/scrollable_widgets1_expected.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected.pdf.0.png
new file mode 100644
index 0000000..4405a36
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets1_expected_mac.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_mac.pdf.0.png
new file mode 100644
index 0000000..723e851
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets1_expected_win.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_win.pdf.0.png
new file mode 100644
index 0000000..25b95ec
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1_expected_win.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets2.evt b/testing/resources/pixel/scrollable_widgets2.evt
new file mode 100644
index 0000000..59b8a4b
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2.evt
@@ -0,0 +1,34 @@
+# Must move the mouse and click to give widget focus.
+mousemove,150,415
+mousedown,left,150,415
+# Scroll all the way down and then scroll back up a bit.
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
diff --git a/testing/resources/pixel/scrollable_widgets2.in b/testing/resources/pixel/scrollable_widgets2.in
new file mode 100644
index 0000000..9453b7c
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2.in
@@ -0,0 +1,52 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R]
+    /DR <<
+      /Font <<
+        /F1 4 0 R
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 300 600]
+  /Annots [5 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelect)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 400 200 430]
+  /Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
+        (Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
+        (Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
+        (Voavanga) (Wolfberry) (Xigua) (Yangmei) (Zucchini)]
+  /V (Banana)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/scrollable_widgets2_expected.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected.pdf.0.png
new file mode 100644
index 0000000..b965ffc
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets2_expected_mac.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_mac.pdf.0.png
new file mode 100644
index 0000000..385f87f
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets2_expected_win.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_win.pdf.0.png
new file mode 100644
index 0000000..292c873
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2_expected_win.pdf.0.png
Binary files differ