Implement FORM_OnFocus() API.

Given a point, if there is an annotation at the point, give if focus if
it is not focus already. If there is no annotation at the point, then
remove the focus from the focused annotation.

BUG=chromium:754594

Change-Id: Iec3070472bbbfbad9d86e517f25da560f82efd4e
Reviewed-on: https://pdfium-review.googlesource.com/12530
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
diff --git a/fpdfsdk/cpdfsdk_pageview.cpp b/fpdfsdk/cpdfsdk_pageview.cpp
index 2155b5d..5bdd720 100644
--- a/fpdfsdk/cpdfsdk_pageview.cpp
+++ b/fpdfsdk/cpdfsdk_pageview.cpp
@@ -259,6 +259,17 @@
   }
 }
 
+bool CPDFSDK_PageView::OnFocus(const CFX_PointF& point, uint32_t nFlag) {
+  CPDFSDK_Annot::ObservedPtr pAnnot(GetFXWidgetAtPoint(point));
+  if (!pAnnot) {
+    m_pFormFillEnv->KillFocusAnnot(nFlag);
+    return false;
+  }
+
+  m_pFormFillEnv->SetFocusAnnot(&pAnnot);
+  return true;
+}
+
 bool CPDFSDK_PageView::OnLButtonDown(const CFX_PointF& point, uint32_t nFlag) {
   CPDFSDK_Annot::ObservedPtr pAnnot(GetFXWidgetAtPoint(point));
   if (!pAnnot) {
diff --git a/fpdfsdk/cpdfsdk_pageview.h b/fpdfsdk/cpdfsdk_pageview.h
index c23aaa7..00e90d7 100644
--- a/fpdfsdk/cpdfsdk_pageview.h
+++ b/fpdfsdk/cpdfsdk_pageview.h
@@ -64,6 +64,7 @@
   CFX_WideString GetSelectedText();
   void ReplaceSelection(const CFX_WideString& text);
 
+  bool OnFocus(const CFX_PointF& point, uint32_t nFlag);
   bool OnLButtonDown(const CFX_PointF& point, uint32_t nFlag);
   bool OnLButtonUp(const CFX_PointF& point, uint32_t nFlag);
 #ifdef PDF_ENABLE_XFA
diff --git a/fpdfsdk/fpdfformfill.cpp b/fpdfsdk/fpdfformfill.cpp
index b36c1df..1b5cc48 100644
--- a/fpdfsdk/fpdfformfill.cpp
+++ b/fpdfsdk/fpdfformfill.cpp
@@ -291,6 +291,17 @@
   return pPageView->OnMouseMove(CFX_PointF(page_x, page_y), modifier);
 }
 
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnFocus(FPDF_FORMHANDLE hHandle,
+                                                 FPDF_PAGE page,
+                                                 int modifier,
+                                                 double page_x,
+                                                 double page_y) {
+  CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page);
+  if (!pPageView)
+    return false;
+  return pPageView->OnFocus(CFX_PointF(page_x, page_y), modifier);
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle,
                                                        FPDF_PAGE page,
                                                        int modifier,
diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c
index 5bad9c8..6f84f35 100644
--- a/fpdfsdk/fpdfview_c_api_test.c
+++ b/fpdfsdk/fpdfview_c_api_test.c
@@ -181,6 +181,7 @@
     CHK(FORM_DoDocumentAAction);
     CHK(FORM_DoPageAAction);
     CHK(FORM_OnMouseMove);
+    CHK(FORM_OnFocus);
     CHK(FORM_OnLButtonDown);
     CHK(FORM_OnLButtonUp);
 #ifdef PDF_ENABLE_XFA
diff --git a/public/fpdf_formfill.h b/public/fpdf_formfill.h
index 6a90140..bf385b3 100644
--- a/public/fpdf_formfill.h
+++ b/public/fpdf_formfill.h
@@ -1242,6 +1242,29 @@
                                                      double page_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
+ *          is no annotation at the point, remove form focus.
+ * Parameters:
+ *          hHandle     -   Handle to the form fill module. Returned by
+ *                          FPDFDOC_InitFormFillEnvironment.
+ *          page        -   Handle to the page. Returned by FPDF_LoadPage.
+ *          modifier    -   Indicates whether various virtual keys are down.
+ *          page_x      -   Specifies the x-coordinate of the cursor in PDF user
+ *                          space.
+ *          page_y      -   Specifies the y-coordinate of the cursor in PDF user
+ *                          space.
+ * Return Value:
+ *          TRUE if there is an annotation at the given point and it has focus.
+ **/
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnFocus(FPDF_FORMHANDLE hHandle,
+                                                 FPDF_PAGE page,
+                                                 int modifier,
+                                                 double page_x,
+                                                 double page_y);
+
+/**
  * Function: FORM_OnLButtonDown
  *          You can call this member function when the user presses the left
  *mouse button.
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index 31235f0..b94b674 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -965,6 +965,14 @@
       } else {
         fprintf(stderr, "mousemove: bad args\n");
       }
+    } else if (tokens[0] == "focus") {
+      if (tokens.size() == 3) {
+        int x = atoi(tokens[1].c_str());
+        int y = atoi(tokens[2].c_str());
+        FORM_OnFocus(form, page, 0, x, y);
+      } else {
+        fprintf(stderr, "focus: bad args\n");
+      }
     } else {
       fprintf(stderr, "Unrecognized event: %s\n", tokens[0].c_str());
     }
diff --git a/testing/resources/javascript/mouse_events.evt b/testing/resources/javascript/mouse_events.evt
new file mode 100644
index 0000000..5710506
--- /dev/null
+++ b/testing/resources/javascript/mouse_events.evt
@@ -0,0 +1,19 @@
+# Mouse in, mouse out.
+mousemove,125,225
+mousemove,125,100
+# Mouse in, click in field.
+mousemove,125,225
+mousedown,left,125,225
+mouseup,left,125,225
+# Mouse out, click elsewhere.
+# This should trigger an On Blur event. (Bug 881)
+mousemove,125,100
+mousedown,left,125,100
+mouseup,left,125,100
+# Mouse in, focus.
+mousemove,125,225
+focus,125,225
+# Mouse out, unfocus.
+# This should trigger an On Blur event. (Bug 881)
+mousemove,125,100
+focus,125,100
diff --git a/testing/resources/javascript/mouse_events.in b/testing/resources/javascript/mouse_events.in
new file mode 100644
index 0000000..a72162a
--- /dev/null
+++ b/testing/resources/javascript/mouse_events.in
@@ -0,0 +1,92 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm 4 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [
+    3 0 R
+  ]
+>>
+endobj
+% Page number 0.
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <</F1 20 0 R>>
+  >>
+  /Annots [ 5 0 R ]
+  /Contents [21 0 R]
+  /MediaBox [0 0 612 792]
+>>
+% Forms
+{{object 4 0}} <<
+  /Fields [5 0 R]
+>>
+% Field with actions:
+% Cursor enter: E
+% Cursor exit: X
+% Mouse down: D
+% Mouse up: U
+% Focus: Fo
+% Blur: Bl
+{{object 5 0}} <<
+ /FT /Tx
+ /T (MyField)
+ /Type /Annot
+ /Subtype /Widget
+ /Rect [100 200 150 250]
+ /AA <<
+   /E 10 0 R
+   /X 11 0 R
+   /D 12 0 R
+   /U 13 0 R
+   /Fo 14 0 R
+   /Bl 15 0 R
+ >>
+>>
+{{object 10 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (app.alert\("enter"\);)
+>>
+endobj
+{{object 11 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (app.alert\("exit"\);)
+>>
+endobj
+{{object 12 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (app.alert\("down"\);)
+>>
+endobj
+{{object 13 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (app.alert\("up"\);)
+>>
+endobj
+{{object 14 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (app.alert\("focus"\);)
+>>
+endobj
+{{object 15 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (app.alert\("blur"\);)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/mouse_events_expected.txt b/testing/resources/javascript/mouse_events_expected.txt
new file mode 100644
index 0000000..3d9b8b6
--- /dev/null
+++ b/testing/resources/javascript/mouse_events_expected.txt
@@ -0,0 +1,10 @@
+Alert: enter
+Alert: exit
+Alert: enter
+Alert: down
+Alert: focus
+Alert: up
+Alert: exit
+Alert: enter
+Alert: focus
+Alert: exit
diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py
index 9524b74..86e0563 100644
--- a/testing/tools/test_runner.py
+++ b/testing/tools/test_runner.py
@@ -115,7 +115,7 @@
     txt_path = os.path.join(self.working_dir, input_root + '.txt')
 
     with open(txt_path, 'w') as outfile:
-      cmd_to_run = [self.pdfium_test_path, pdf_path]
+      cmd_to_run = [self.pdfium_test_path, '--send-events', pdf_path]
       subprocess.check_call(cmd_to_run, stdout=outfile)
 
     cmd = [sys.executable, self.text_diff_path, expected_txt_path, txt_path]