Sending focused annotation update to the host
Adding a new method FFI_OnFocusChange() in FPDF_FORMFILLINFO with
version = 2 which will send the focused annotation whenever it changes.
This will be used to bring the focused annotation into view via
scrolling.
Bug: chromium:989027
Change-Id: Ie0d1523f14234a6738ad1f390dd1deb09b03834d
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/59270
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Aayush Dhir <aadhir@microsoft.com>
diff --git a/fpdfsdk/cpdfsdk_formfillenvironment.cpp b/fpdfsdk/cpdfsdk_formfillenvironment.cpp
index feeb2b4..243528e 100644
--- a/fpdfsdk/cpdfsdk_formfillenvironment.cpp
+++ b/fpdfsdk/cpdfsdk_formfillenvironment.cpp
@@ -10,6 +10,7 @@
#include <utility>
#include <vector>
+#include "core/fpdfapi/page/cpdf_annotcontext.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfdoc/cpdf_nametree.h"
@@ -706,6 +707,10 @@
return false;
m_pFocusAnnot.Reset(pAnnot->Get());
+
+ // If we are not able to inform the client about the focus change, it
+ // shouldn't be considered as failure.
+ SendOnFocusChange(pAnnot);
return true;
}
@@ -747,3 +752,31 @@
bool CPDFSDK_FormFillEnvironment::HasPermissions(uint32_t flags) const {
return !!(m_pCPDFDoc->GetUserPermissions() & flags);
}
+
+void CPDFSDK_FormFillEnvironment::SendOnFocusChange(
+ ObservedPtr<CPDFSDK_Annot>* pAnnot) {
+ if (!m_pInfo || m_pInfo->version < 2 || !m_pInfo->FFI_OnFocusChange)
+ return;
+
+ if ((*pAnnot)->AsXFAWidget()) {
+ // TODO(crbug.com/pdfium/1482): Handle XFA case.
+ return;
+ }
+
+ CPDFSDK_PageView* pPageView = (*pAnnot)->GetPageView();
+ if (!pPageView || !pPageView->IsValid())
+ return;
+
+ CPDF_Page* cpdf_page = (*pAnnot)->GetPDFPage();
+ if (!cpdf_page)
+ return;
+
+ CPDF_Dictionary* annot_dict = (*pAnnot)->GetPDFAnnot()->GetAnnotDict();
+
+ auto focused_annot =
+ pdfium::MakeUnique<CPDF_AnnotContext>(annot_dict, cpdf_page);
+ FPDF_ANNOTATION fpdf_annot =
+ FPDFAnnotationFromCPDFAnnotContext(focused_annot.get());
+
+ m_pInfo->FFI_OnFocusChange(m_pInfo, fpdf_annot, pPageView->GetPageIndex());
+}
\ No newline at end of file
diff --git a/fpdfsdk/cpdfsdk_formfillenvironment.h b/fpdfsdk/cpdfsdk_formfillenvironment.h
index 71a4fa8..d7ccc23 100644
--- a/fpdfsdk/cpdfsdk_formfillenvironment.h
+++ b/fpdfsdk/cpdfsdk_formfillenvironment.h
@@ -209,6 +209,7 @@
private:
IPDF_Page* GetPage(int nIndex);
+ void SendOnFocusChange(ObservedPtr<CPDFSDK_Annot>* pAnnot);
FPDF_FORMFILLINFO* const m_pInfo;
std::unique_ptr<CPDFSDK_ActionHandler> m_pActionHandler;
diff --git a/fpdfsdk/fpdf_formfill.cpp b/fpdfsdk/fpdf_formfill.cpp
index a3f3022..085fec3 100644
--- a/fpdfsdk/fpdf_formfill.cpp
+++ b/fpdfsdk/fpdf_formfill.cpp
@@ -226,6 +226,20 @@
#endif
}
+// Returns true if formfill version is correctly set. See |version| in
+// FPDF_FORMFILLINFO for details regarding correct version.
+bool CheckFormfillVersion(FPDF_FORMFILLINFO* formInfo) {
+ if (!formInfo || formInfo->version < 1 || formInfo->version > 2)
+ return false;
+
+#ifdef PDF_ENABLE_XFA
+ if (formInfo->version != 2)
+ return false;
+#endif // PDF_ENABLE_XFA
+
+ return true;
+}
+
} // namespace
FPDF_EXPORT int FPDF_CALLCONV
@@ -285,12 +299,7 @@
FPDF_EXPORT FPDF_FORMHANDLE FPDF_CALLCONV
FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document,
FPDF_FORMFILLINFO* formInfo) {
-#ifdef PDF_ENABLE_XFA
- constexpr int kRequiredVersion = 2;
-#else // PDF_ENABLE_XFA
- constexpr int kRequiredVersion = 1;
-#endif // PDF_ENABLE_XFA
- if (!formInfo || formInfo->version != kRequiredVersion)
+ if (!CheckFormfillVersion(formInfo))
return nullptr;
auto* pDocument = CPDFDocumentFromFPDFDocument(document);
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index 3ae272b..e186f00 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -523,12 +523,21 @@
static constexpr float kSingleFormLastSelectedYSecondVisibleOption = 108.0;
};
+class FPDFFormFillTextFormEmbedderTestVersion2
+ : public FPDFFormFillTextFormEmbedderTest {
+ void SetUp() override {
+ SetFormFillInfoVersion(2);
+ FPDFFormFillInteractiveEmbedderTest::SetUp();
+ }
+};
+
TEST_F(FPDFFormFillEmbedderTest, FirstTest) {
EmbedderTestMockDelegate mock;
EXPECT_CALL(mock, Alert(_, _, _, _)).Times(0);
EXPECT_CALL(mock, UnsupportedHandler(_)).Times(0);
EXPECT_CALL(mock, SetTimer(_, _)).Times(0);
EXPECT_CALL(mock, KillTimer(_)).Times(0);
+ EXPECT_CALL(mock, OnFocusChange(_, _, _)).Times(0);
SetDelegate(&mock);
EXPECT_TRUE(OpenDocument("hello_world.pdf"));
@@ -2056,6 +2065,30 @@
CheckSelection(L"Hello World");
}
+TEST_F(FPDFFormFillTextFormEmbedderTest, FocusAnnotationUpdateToEmbedder) {
+ testing::NiceMock<EmbedderTestMockDelegate> mock;
+ SetDelegate(&mock);
+ CheckFocusedFieldText(L"");
+
+#ifdef PDF_ENABLE_XFA
+ EXPECT_CALL(mock, OnFocusChange(_, _, 0)).Times(1);
+#else // PDF_ENABLE_XFA
+ EXPECT_CALL(mock, OnFocusChange(_, _, 0)).Times(0);
+#endif // PDF_ENABLE_XFA
+
+ ClickOnFormFieldAtPoint(RegularFormBegin());
+}
+
+TEST_F(FPDFFormFillTextFormEmbedderTestVersion2,
+ FocusAnnotationUpdateToEmbedder) {
+ testing::NiceMock<EmbedderTestMockDelegate> mock;
+ SetDelegate(&mock);
+ CheckFocusedFieldText(L"");
+
+ EXPECT_CALL(mock, OnFocusChange(_, _, 0)).Times(1);
+ ClickOnFormFieldAtPoint(RegularFormBegin());
+}
+
TEST_F(FPDFFormFillTextFormEmbedderTest, FocusChanges) {
static const CFX_PointF kNonFormPoint(1, 1);
CheckFocusedFieldText(L"");
diff --git a/public/fpdf_formfill.h b/public/fpdf_formfill.h
index 95c24b4..c383212 100644
--- a/public/fpdf_formfill.h
+++ b/public/fpdf_formfill.h
@@ -374,8 +374,17 @@
typedef struct _FPDF_FORMFILLINFO {
/*
- * Version number of the interface. Currently must be 1 (when PDFium is built
- * without the XFA module) or must be 2 (when built with the XFA module).
+ * Version number of the interface.
+ * Version 1 contains stable interfaces. Version 2 has additional
+ * experimental interfaces.
+ * When PDFium is built without the XFA module, version can be 1 or 2.
+ * With version 1, only stable interfaces are called. With version 2,
+ * additional experimental interfaces are also called.
+ * When PDFium is built with the XFA module, version must be 2.
+ * All the XFA related interfaces are experimental. If PDFium is built with
+ * the XFA module and version 1 then none of the XFA related interfaces
+ * would be called. When PDFium is built with XFA module then the version
+ * must be 2.
*/
int version;
@@ -1069,6 +1078,30 @@
FPDF_WIDESTRING wsURL,
FPDF_WIDESTRING wsData,
FPDF_WIDESTRING wsEncode);
+
+ /*
+ * Method: FFI_OnFocusChange
+ * Called when the focused annotation is updated.
+ * Interface Version:
+ * Ignored if |version| < 2.
+ * Implementation Required:
+ * No
+ * Parameters:
+ * param - Pointer to the interface structure itself.
+ * annot - The focused annotation.
+ * page_index - Index number of the page which contains the
+ * focused annotation. 0 for the first page.
+ * Return value:
+ * None.
+ * Comments:
+ * This callback function is useful for implementing any view based
+ * action such as scrolling the annotation rect into view. The
+ * embedder should not copy and store the annot as its scope is
+ * limited to this call only.
+ */
+ void (*FFI_OnFocusChange)(struct _FPDF_FORMFILLINFO* param,
+ FPDF_ANNOTATION annot,
+ int page_index);
} FPDF_FORMFILLINFO;
/*
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 9bf4a18..291b8d7 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -247,15 +247,12 @@
FPDF_FORMFILLINFO* formfillinfo = static_cast<FPDF_FORMFILLINFO*>(this);
memset(formfillinfo, 0, sizeof(FPDF_FORMFILLINFO));
-#ifdef PDF_ENABLE_XFA
- formfillinfo->version = 2;
-#else // PDF_ENABLE_XFA
- formfillinfo->version = 1;
-#endif // PDF_ENABLE_XFA
+ formfillinfo->version = form_fill_info_version_;
formfillinfo->FFI_SetTimer = SetTimerTrampoline;
formfillinfo->FFI_KillTimer = KillTimerTrampoline;
formfillinfo->FFI_GetPage = GetPageTrampoline;
formfillinfo->FFI_DoURIAction = DoURIActionTrampoline;
+ formfillinfo->FFI_OnFocusChange = OnFocusChangeTrampoline;
if (javascript_option == JavaScriptOption::kEnableJavaScript)
formfillinfo->m_pJsPlatform = platform;
@@ -609,6 +606,14 @@
}
// static
+void EmbedderTest::OnFocusChangeTrampoline(FPDF_FORMFILLINFO* info,
+ FPDF_ANNOTATION annot,
+ int page_index) {
+ EmbedderTest* test = static_cast<EmbedderTest*>(info);
+ return test->delegate_->OnFocusChange(info, annot, page_index);
+}
+
+// static
std::string EmbedderTest::HashBitmap(FPDF_BITMAP bitmap) {
int stride = FPDFBitmap_GetStride(bitmap);
int usable_bytes_per_row =
diff --git a/testing/embedder_test.h b/testing/embedder_test.h
index 662333c..5d42442 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -64,6 +64,11 @@
// Equivalent to FPDF_FORMFILLINFO::FFI_DoURIAction().
virtual void DoURIAction(FPDF_BYTESTRING uri) {}
+
+ // Equivalent to FPDF_FORMFILLINFO::FFI_OnFocusChange().
+ virtual void OnFocusChange(FPDF_FORMFILLINFO* info,
+ FPDF_ANNOTATION annot,
+ int page_index) {}
};
EmbedderTest();
@@ -81,6 +86,10 @@
delegate_ = delegate ? delegate : default_delegate_.get();
}
+ void SetFormFillInfoVersion(int form_fill_info_version) {
+ form_fill_info_version_ = form_fill_info_version;
+ }
+
FPDF_DOCUMENT document() const { return document_; }
FPDF_FORMHANDLE form_handle() const { return form_handle_; }
@@ -248,6 +257,12 @@
std::unique_ptr<Delegate> default_delegate_;
Delegate* delegate_;
+#ifdef PDF_ENABLE_XFA
+ int form_fill_info_version_ = 2;
+#else // PDF_ENABLE_XFA
+ int form_fill_info_version_ = 1;
+#endif // PDF_ENABLE_XFA
+
FPDF_DOCUMENT document_ = nullptr;
FPDF_FORMHANDLE form_handle_ = nullptr;
FPDF_AVAIL avail_ = nullptr;
@@ -284,6 +299,9 @@
int page_index);
static void DoURIActionTrampoline(FPDF_FORMFILLINFO* info,
FPDF_BYTESTRING uri);
+ static void OnFocusChangeTrampoline(FPDF_FORMFILLINFO* info,
+ FPDF_ANNOTATION annot,
+ int page_index);
static int WriteBlockCallback(FPDF_FILEWRITE* pFileWrite,
const void* data,
unsigned long size);
diff --git a/testing/embedder_test_mock_delegate.h b/testing/embedder_test_mock_delegate.h
index c3f2820..e9bd70e 100644
--- a/testing/embedder_test_mock_delegate.h
+++ b/testing/embedder_test_mock_delegate.h
@@ -8,7 +8,7 @@
#include "testing/embedder_test.h"
#include "testing/gmock/include/gmock/gmock.h"
-class EmbedderTestMockDelegate final : public EmbedderTest::Delegate {
+class EmbedderTestMockDelegate : public EmbedderTest::Delegate {
public:
MOCK_METHOD1(UnsupportedHandler, void(int type));
MOCK_METHOD4(
@@ -16,6 +16,10 @@
int(FPDF_WIDESTRING message, FPDF_WIDESTRING title, int type, int icon));
MOCK_METHOD2(SetTimer, int(int msecs, TimerCallback fn));
MOCK_METHOD1(KillTimer, void(int msecs));
+ MOCK_METHOD3(OnFocusChange,
+ void(FPDF_FORMFILLINFO* info,
+ FPDF_ANNOTATION annot,
+ int page_index));
};
#endif // TESTING_EMBEDDER_TEST_MOCK_DELEGATE_H_