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