Added URI Action handling public API and its test cases
Support for opening a link in new tab/window exists using mouse. But
after setting focus over link by pressing Tab key, we do not have the
provision to open the link in new tab/window using keyboard shortcut.
This new API carries keyboard modifier value whenever URI action
needs to be performed.
Embedder tests are added to validate the added APIs.
Bug: chromium:994500
Change-Id: Id1bb8d6dd5c607bc5194d300250d8b796c7ff03c
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/66830
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Badhri Ravikumar <bravi@microsoft.com>
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index dfee33c..a12e3b0 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -21,7 +21,9 @@
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
+using testing::InSequence;
using testing::NiceMock;
+using testing::StrEq;
using FPDFFormFillEmbedderTest = EmbedderTest;
@@ -590,6 +592,7 @@
EXPECT_CALL(mock, KillTimer(_)).Times(0);
EXPECT_CALL(mock, OnFocusChange(_, _, _)).Times(0);
EXPECT_CALL(mock, DoURIAction(_)).Times(0);
+ EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(_, _, _)).Times(0);
SetDelegate(&mock);
EXPECT_TRUE(OpenDocument("hello_world.pdf"));
@@ -2976,6 +2979,13 @@
ASSERT_TRUE(OpenDocument("annots_action_handling.pdf"));
page_ = LoadPage(0);
ASSERT_TRUE(page_);
+
+ // Set Widget and Link as supported tabbable annots.
+ constexpr FPDF_ANNOTATION_SUBTYPE kFocusableSubtypes[] = {FPDF_ANNOT_WIDGET,
+ FPDF_ANNOT_LINK};
+ constexpr size_t kSubtypeCount = FX_ArraySize(kFocusableSubtypes);
+ ASSERT_TRUE(FPDFAnnot_SetFocusableSubtypes(
+ form_handle(), kFocusableSubtypes, kSubtypeCount));
}
void TearDown() override {
@@ -2983,10 +2993,14 @@
EmbedderTest::TearDown();
}
- void SetFocusOnFirstForm() {
+ void SetFocusOnNthAnnot(size_t n) {
+ DCHECK_NE(n, 0);
+ // Setting focus on first annot.
FORM_OnMouseMove(form_handle(), page(), /*modifier=*/0, 100, 680);
FORM_OnLButtonDown(form_handle(), page(), /*modifier=*/0, 100, 680);
FORM_OnLButtonUp(form_handle(), page(), /*modifier=*/0, 100, 680);
+ for (size_t i = 1; i < n; i++)
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Tab, 0));
}
FPDF_PAGE page() { return page_; }
@@ -3001,7 +3015,7 @@
EXPECT_CALL(mock, DoURIAction(_)).Times(0);
SetDelegate(&mock);
- SetFocusOnFirstForm();
+ SetFocusOnNthAnnot(1);
// Tab once from first form to go to button widget.
ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Tab, 0));
@@ -3010,3 +3024,62 @@
// handling key press implementation on buttons.
ASSERT_FALSE(FORM_OnChar(form_handle(), page(), FWL_VKEY_Return, 0));
}
+
+TEST_F(FPDFFormFillActionUriTest, LinkActionInvokeTest) {
+ NiceMock<EmbedderTestMockDelegate> mock;
+ {
+ InSequence sequence;
+ // TODO(crbug.com/994500) : DoURIAction number of expected calls to be
+ // updated to 4.
+ const char kExpectedUri[] = "https://www.cs.chromium.org/";
+ EXPECT_CALL(mock, DoURIAction(StrEq(kExpectedUri))).Times(0);
+ EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(_, _, _)).Times(0);
+ }
+ SetDelegate(&mock);
+ SetFocusOnNthAnnot(3);
+ int modifier = 0;
+ // TODO(crbug.com/994500): Following asserts to be changed to ASSERT_TRUE.
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ modifier = FWL_EVENTFLAG_ControlKey;
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ modifier = FWL_EVENTFLAG_ShiftKey;
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ modifier |= FWL_EVENTFLAG_ControlKey;
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+}
+
+class FPDFFormFillActionUriTestVersion2 : public FPDFFormFillActionUriTest {
+ void SetUp() override {
+ SetFormFillInfoVersion(2);
+ FPDFFormFillActionUriTest::SetUp();
+ }
+};
+
+TEST_F(FPDFFormFillActionUriTestVersion2, LinkActionInvokeTest) {
+ NiceMock<EmbedderTestMockDelegate> mock;
+ {
+ InSequence sequence;
+ EXPECT_CALL(mock, DoURIAction(_)).Times(0);
+ // TODO(crbug.com/994500): Following comments has to be removed.
+ // const char kExpectedUri[] = "https://www.cs.chromium.org/";
+ // EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(
+ // _, StrEq(kExpectedUri), 0));
+ // EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(
+ // _, StrEq(kExpectedUri), FWL_EVENTFLAG_ControlKey));
+ // EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(
+ // _, StrEq(kExpectedUri), FWL_EVENTFLAG_ShiftKey));
+ // EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(
+ // _, StrEq(kExpectedUri), 3));
+ }
+ SetDelegate(&mock);
+ SetFocusOnNthAnnot(3);
+ int modifier = 0;
+ // TODO(crbug.com/994500): Following asserts to be changed to ASSERT_TRUE.
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ modifier = FWL_EVENTFLAG_ControlKey;
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ modifier = FWL_EVENTFLAG_ShiftKey;
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ modifier |= FWL_EVENTFLAG_ControlKey;
+ ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+}
\ No newline at end of file
diff --git a/public/fpdf_formfill.h b/public/fpdf_formfill.h
index 1f656b0..4ec345f 100644
--- a/public/fpdf_formfill.h
+++ b/public/fpdf_formfill.h
@@ -680,6 +680,10 @@
* Return value:
* None.
* Comments:
+ * If the embedder is version 2 or higher and have implementation for
+ * FFI_DoURIActionWithKeyboardModifier, then
+ * FFI_DoURIActionWithKeyboardModifier takes precedence over
+ * FFI_DoURIAction.
* See the URI actions description of <<PDF Reference, version 1.7>>
* for more details.
*/
@@ -1102,6 +1106,32 @@
void (*FFI_OnFocusChange)(struct _FPDF_FORMFILLINFO* param,
FPDF_ANNOTATION annot,
int page_index);
+
+ /**
+ * Method: FFI_DoURIActionWithKeyboardModifier
+ * Ask the implementation to navigate to a uniform resource identifier
+ * with the specified modifiers.
+ * Interface Version:
+ * Ignored if |version| < 2.
+ * Implementation Required:
+ * No
+ * Parameters:
+ * param - Pointer to the interface structure itself.
+ * uri - A byte string which indicates the uniform
+ * resource identifier, terminated by 0.
+ * modifiers - Keyboard modifier that indicates which of
+ * the virtual keys are down, if any.
+ * Return value:
+ * None.
+ * Comments:
+ * If the embedder who is version 2 and does not implement this API,
+ * then a call will be redirected to FFI_DoURIAction.
+ * See the URI actions description of <<PDF Reference, version 1.7>>
+ * for more details.
+ */
+ void(*FFI_DoURIActionWithKeyboardModifier)(struct _FPDF_FORMFILLINFO* param,
+ FPDF_BYTESTRING uri,
+ int modifiers);
} FPDF_FORMFILLINFO;
/*
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 291b8d7..caba758 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -253,6 +253,8 @@
formfillinfo->FFI_GetPage = GetPageTrampoline;
formfillinfo->FFI_DoURIAction = DoURIActionTrampoline;
formfillinfo->FFI_OnFocusChange = OnFocusChangeTrampoline;
+ formfillinfo->FFI_DoURIActionWithKeyboardModifier =
+ DoURIActionWithKeyboardModifierTrampoline;
if (javascript_option == JavaScriptOption::kEnableJavaScript)
formfillinfo->m_pJsPlatform = platform;
@@ -614,6 +616,15 @@
}
// static
+void EmbedderTest::DoURIActionWithKeyboardModifierTrampoline(
+ FPDF_FORMFILLINFO* info,
+ FPDF_BYTESTRING uri,
+ int modifiers) {
+ EmbedderTest* test = static_cast<EmbedderTest*>(info);
+ return test->delegate_->DoURIActionWithKeyboardModifier(info, uri, modifiers);
+}
+
+// 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 5d42442..e9e69fb 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -69,6 +69,11 @@
virtual void OnFocusChange(FPDF_FORMFILLINFO* info,
FPDF_ANNOTATION annot,
int page_index) {}
+
+ // Equivalent to FPDF_FORMFILLINFO::FFI_DoURIActionWithKeyboardModifier().
+ virtual void DoURIActionWithKeyboardModifier(FPDF_FORMFILLINFO* info,
+ FPDF_BYTESTRING uri,
+ int modifiers) {}
};
EmbedderTest();
@@ -302,6 +307,9 @@
static void OnFocusChangeTrampoline(FPDF_FORMFILLINFO* info,
FPDF_ANNOTATION annot,
int page_index);
+ static void DoURIActionWithKeyboardModifierTrampoline(FPDF_FORMFILLINFO* info,
+ FPDF_BYTESTRING uri,
+ int modifiers);
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 06f144f..f2cbb84 100644
--- a/testing/embedder_test_mock_delegate.h
+++ b/testing/embedder_test_mock_delegate.h
@@ -21,6 +21,10 @@
void(FPDF_FORMFILLINFO* info,
FPDF_ANNOTATION annot,
int page_index));
+ MOCK_METHOD3(DoURIActionWithKeyboardModifier,
+ void(FPDF_FORMFILLINFO* info,
+ FPDF_BYTESTRING uri,
+ int modifiers));
};
#endif // TESTING_EMBEDDER_TEST_MOCK_DELEGATE_H_
diff --git a/testing/resources/annots_action_handling.in b/testing/resources/annots_action_handling.in
index bbf1a48..51becc7 100644
--- a/testing/resources/annots_action_handling.in
+++ b/testing/resources/annots_action_handling.in
@@ -14,20 +14,32 @@
{{object 3 0}} <<
/Type /Page
/Parent 2 0 R
- /Annots [4 0 R 5 0 R]
+ /Annots [5 0 R 6 0 R 7 0 R]
+ /Contents 4 0 R
/Tabs /R
>>
endobj
{{object 4 0}} <<
+ {{streamlen}}
+>>
+stream
+BT
+70 340 Td
+14 Tf
+(External Link ) Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
/Type /Annot
/Subtype /Widget
/FT /Tx
/Parent 3 0 R
- /T (Sub_LeftBottom)
+ /T (TextField)
/Rect [69 670 220 690]
>>
endobj
-{{object 5 0}} <<
+{{object 6 0}} <<
/Type /Annot
/Subtype /Widget
/FT /Btn
@@ -41,6 +53,18 @@
/Ff 65536
>>
endobj
+{{object 7 0}} <<
+ /Type /Annot
+ /Subtype /Link
+ /Rect [69 338 180 358]
+ /A <<
+ /Type /Action
+ /S /URI
+ /URI (https://www.cs.chromium.org/)
+ >>
+ /F 4
+>>
+endobj
{{xref}}
{{trailer}}
{{startxref}}
diff --git a/testing/resources/annots_action_handling.pdf b/testing/resources/annots_action_handling.pdf
index 70e62f7..49ea926 100644
--- a/testing/resources/annots_action_handling.pdf
+++ b/testing/resources/annots_action_handling.pdf
@@ -15,20 +15,32 @@
3 0 obj <<
/Type /Page
/Parent 2 0 R
- /Annots [4 0 R 5 0 R]
+ /Annots [5 0 R 6 0 R 7 0 R]
+ /Contents 4 0 R
/Tabs /R
>>
endobj
4 0 obj <<
+ /Length 42
+>>
+stream
+BT
+70 340 Td
+14 Tf
+(External Link ) Tj
+ET
+endstream
+endobj
+5 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Tx
/Parent 3 0 R
- /T (Sub_LeftBottom)
+ /T (TextField)
/Rect [69 670 220 690]
>>
endobj
-5 0 obj <<
+6 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Btn
@@ -42,18 +54,32 @@
/Ff 65536
>>
endobj
+7 0 obj <<
+ /Type /Annot
+ /Subtype /Link
+ /Rect [69 338 180 358]
+ /A <<
+ /Type /Action
+ /S /URI
+ /URI (https://www.cs.chromium.org/)
+ >>
+ /F 4
+>>
+endobj
xref
-0 6
+0 8
0000000000 65535 f
0000000015 00000 n
0000000094 00000 n
0000000157 00000 n
-0000000243 00000 n
-0000000371 00000 n
+0000000267 00000 n
+0000000360 00000 n
+0000000483 00000 n
+0000000666 00000 n
trailer <<
/Root 1 0 R
- /Size 6
+ /Size 8
>>
startxref
-554
+834
%%EOF
\ No newline at end of file