Implemented URI Action Handling for links
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 shortcuts.
This implementation uses FFI_DoURIActionWithKeyboardModifier API
which handles opening the link in new tab/window.
CL for tests : https://pdfium-review.googlesource.com/c/pdfium/+/66830
Bug: chromium:994500
Change-Id: I65500983d1a07e9b31743d0a0df88a1bde4eb142
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/68930
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Badhri Ravikumar <bravi@microsoft.com>
diff --git a/core/fpdfdoc/cpdf_aaction.cpp b/core/fpdfdoc/cpdf_aaction.cpp
index 300e94a..46fe173 100644
--- a/core/fpdfdoc/cpdf_aaction.cpp
+++ b/core/fpdfdoc/cpdf_aaction.cpp
@@ -57,10 +57,11 @@
}
// static
-bool CPDF_AAction::IsUserClick(AActionType eType) {
- switch (eType) {
+bool CPDF_AAction::IsUserInput(AActionType type) {
+ switch (type) {
case kButtonUp:
case kButtonDown:
+ case kKeyStroke:
return true;
default:
return false;
diff --git a/core/fpdfdoc/cpdf_aaction.h b/core/fpdfdoc/cpdf_aaction.h
index c3c65e8..0076a9c 100644
--- a/core/fpdfdoc/cpdf_aaction.h
+++ b/core/fpdfdoc/cpdf_aaction.h
@@ -48,7 +48,7 @@
CPDF_Action GetAction(AActionType eType) const;
const CPDF_Dictionary* GetDict() const { return m_pDict.Get(); }
- static bool IsUserClick(AActionType eType);
+ static bool IsUserInput(AActionType type);
private:
RetainPtr<const CPDF_Dictionary> const m_pDict;
diff --git a/fpdfsdk/cpdfsdk_actionhandler.cpp b/fpdfsdk/cpdfsdk_actionhandler.cpp
index 6b2b4b9..5e3e83b 100644
--- a/fpdfsdk/cpdfsdk_actionhandler.cpp
+++ b/fpdfsdk/cpdfsdk_actionhandler.cpp
@@ -59,6 +59,22 @@
return false;
}
+bool CPDFSDK_ActionHandler::DoAction_Link(
+ const CPDF_Action& action,
+ CPDF_AAction::AActionType type,
+ CPDFSDK_FormFillEnvironment* form_fill_env,
+ int modifiers) {
+ ASSERT(form_fill_env);
+ if (action.GetType() != CPDF_Action::URI)
+ return false;
+
+ if (!CPDF_AAction::IsUserInput(type))
+ return false;
+
+ DoAction_URI(form_fill_env, action, modifiers);
+ return true;
+}
+
bool CPDFSDK_ActionHandler::DoAction_Page(
const CPDF_Action& action,
enum CPDF_AAction::AActionType eType,
@@ -105,7 +121,7 @@
}
} else {
DoAction_NoJs(action, CPDF_AAction::AActionType::kDocumentOpen,
- pFormFillEnv);
+ pFormFillEnv, /*modifiers=*/0);
}
for (int32_t i = 0, sz = action.GetSubActionsCount(); i < sz; i++) {
@@ -136,7 +152,7 @@
RunDocumentPageJavaScript(pFormFillEnv, type, swJS);
}
} else {
- DoAction_NoJs(action, type, pFormFillEnv);
+ DoAction_NoJs(action, type, pFormFillEnv, /*modifiers=*/0);
}
ASSERT(pFormFillEnv);
@@ -184,7 +200,7 @@
}
}
} else {
- DoAction_NoJs(action, type, pFormFillEnv);
+ DoAction_NoJs(action, type, pFormFillEnv, /*modifiers=*/0);
}
for (int32_t i = 0, sz = action.GetSubActionsCount(); i < sz; i++) {
@@ -200,7 +216,8 @@
void CPDFSDK_ActionHandler::DoAction_NoJs(
const CPDF_Action& action,
CPDF_AAction::AActionType type,
- CPDFSDK_FormFillEnvironment* pFormFillEnv) {
+ CPDFSDK_FormFillEnvironment* pFormFillEnv,
+ int modifiers) {
ASSERT(pFormFillEnv);
switch (action.GetType()) {
@@ -208,8 +225,8 @@
DoAction_GoTo(pFormFillEnv, action);
break;
case CPDF_Action::URI:
- if (CPDF_AAction::IsUserClick(type))
- DoAction_URI(pFormFillEnv, action);
+ if (CPDF_AAction::IsUserInput(type))
+ DoAction_URI(pFormFillEnv, action, modifiers);
break;
case CPDF_Action::Hide:
DoAction_Hide(action, pFormFillEnv);
@@ -218,7 +235,7 @@
DoAction_Named(pFormFillEnv, action);
break;
case CPDF_Action::SubmitForm:
- if (CPDF_AAction::IsUserClick(type))
+ if (CPDF_AAction::IsUserInput(type))
DoAction_SubmitForm(action, pFormFillEnv);
break;
case CPDF_Action::ResetForm:
@@ -268,11 +285,12 @@
void CPDFSDK_ActionHandler::DoAction_URI(
CPDFSDK_FormFillEnvironment* pFormFillEnv,
- const CPDF_Action& action) {
+ const CPDF_Action& action,
+ int modifiers) {
ASSERT(action.GetDict());
ByteString sURI = action.GetURI(pFormFillEnv->GetPDFDocument());
- pFormFillEnv->DoURIAction(sURI.c_str());
+ pFormFillEnv->DoURIAction(sURI.c_str(), modifiers);
}
void CPDFSDK_ActionHandler::DoAction_Named(
diff --git a/fpdfsdk/cpdfsdk_actionhandler.h b/fpdfsdk/cpdfsdk_actionhandler.h
index a8bd9cf..7880eba 100644
--- a/fpdfsdk/cpdfsdk_actionhandler.h
+++ b/fpdfsdk/cpdfsdk_actionhandler.h
@@ -44,6 +44,10 @@
CPDFSDK_FormFillEnvironment* pFormFillEnv,
CPDF_FormField* pFormField,
CPDFSDK_FieldAction* data);
+ bool DoAction_Link(const CPDF_Action& action,
+ CPDF_AAction::AActionType type,
+ CPDFSDK_FormFillEnvironment* form_fill_env,
+ int modifiers);
private:
using RunScriptCallback = std::function<void(IJS_EventContext* context)>;
@@ -68,7 +72,8 @@
void DoAction_NoJs(const CPDF_Action& action,
CPDF_AAction::AActionType type,
- CPDFSDK_FormFillEnvironment* pFormFillEnv);
+ CPDFSDK_FormFillEnvironment* pFormFillEnv,
+ int modifiers);
void RunDocumentPageJavaScript(CPDFSDK_FormFillEnvironment* pFormFillEnv,
CPDF_AAction::AActionType type,
const WideString& script);
@@ -89,7 +94,8 @@
void DoAction_Launch(CPDFSDK_FormFillEnvironment* pFormFillEnv,
const CPDF_Action& action);
void DoAction_URI(CPDFSDK_FormFillEnvironment* pFormFillEnv,
- const CPDF_Action& action);
+ const CPDF_Action& action,
+ int modifiers);
void DoAction_Named(CPDFSDK_FormFillEnvironment* pFormFillEnv,
const CPDF_Action& action);
diff --git a/fpdfsdk/cpdfsdk_baannot.cpp b/fpdfsdk/cpdfsdk_baannot.cpp
index 87866b5..968e8b5 100644
--- a/fpdfsdk/cpdfsdk_baannot.cpp
+++ b/fpdfsdk/cpdfsdk_baannot.cpp
@@ -212,7 +212,7 @@
if (AAction.ActionExist(eAAT))
return AAction.GetAction(eAAT);
- if (eAAT == CPDF_AAction::kButtonUp)
+ if (eAAT == CPDF_AAction::kButtonUp || eAAT == CPDF_AAction::kKeyStroke)
return GetAction();
return CPDF_Action(nullptr);
diff --git a/fpdfsdk/cpdfsdk_baannothandler.cpp b/fpdfsdk/cpdfsdk_baannothandler.cpp
index 10bde5c..ef90835 100644
--- a/fpdfsdk/cpdfsdk_baannothandler.cpp
+++ b/fpdfsdk/cpdfsdk_baannothandler.cpp
@@ -12,11 +12,13 @@
#include "core/fpdfapi/page/cpdf_page.h"
#include "core/fpdfdoc/cpdf_interactiveform.h"
#include "core/fxge/cfx_drawutils.h"
+#include "fpdfsdk/cpdfsdk_actionhandler.h"
#include "fpdfsdk/cpdfsdk_annot.h"
#include "fpdfsdk/cpdfsdk_baannot.h"
#include "fpdfsdk/cpdfsdk_formfillenvironment.h"
#include "fpdfsdk/cpdfsdk_pageview.h"
#include "fpdfsdk/formfiller/cffl_formfiller.h"
+#include "public/fpdf_fwlevent.h"
#include "third_party/base/stl_util.h"
namespace {
@@ -173,7 +175,24 @@
bool CPDFSDK_BAAnnotHandler::OnKeyDown(CPDFSDK_Annot* pAnnot,
int nKeyCode,
int nFlag) {
- return false;
+ ASSERT(pAnnot);
+
+ // OnKeyDown() is implemented only for link annotations for now. As
+ // OnKeyDown() is implemented for other subtypes, following check should be
+ // modified.
+ if (nKeyCode != FWL_VKEY_Return ||
+ pAnnot->GetAnnotSubtype() != CPDF_Annot::Subtype::LINK) {
+ return false;
+ }
+
+ CPDFSDK_BAAnnot* ba_annot = pAnnot->AsBAAnnot();
+ CPDF_Action action = ba_annot->GetAAction(CPDF_AAction::kKeyStroke);
+ if (!action.GetDict() || action.GetType() != CPDF_Action::URI) {
+ return false;
+ }
+
+ return form_fill_environment_->GetActionHandler()->DoAction_Link(
+ action, CPDF_AAction::kKeyStroke, form_fill_environment_.Get(), nFlag);
}
bool CPDFSDK_BAAnnotHandler::OnKeyUp(CPDFSDK_Annot* pAnnot,
diff --git a/fpdfsdk/cpdfsdk_formfillenvironment.cpp b/fpdfsdk/cpdfsdk_formfillenvironment.cpp
index fc479e6..5748dcd 100644
--- a/fpdfsdk/cpdfsdk_formfillenvironment.cpp
+++ b/fpdfsdk/cpdfsdk_formfillenvironment.cpp
@@ -384,8 +384,17 @@
m_pInfo->FFI_SetTextFieldFocus(m_pInfo, focusText, nTextLen, bFocus);
}
-void CPDFSDK_FormFillEnvironment::DoURIAction(const char* bsURI) {
- if (m_pInfo && m_pInfo->FFI_DoURIAction)
+void CPDFSDK_FormFillEnvironment::DoURIAction(const char* bsURI,
+ uint32_t modifiers) {
+ if (!m_pInfo)
+ return;
+
+ if (m_pInfo->version >= 2 && m_pInfo->FFI_DoURIActionWithKeyboardModifier) {
+ m_pInfo->FFI_DoURIActionWithKeyboardModifier(m_pInfo, bsURI, modifiers);
+ return;
+ }
+
+ if (m_pInfo->FFI_DoURIAction)
m_pInfo->FFI_DoURIAction(m_pInfo, bsURI);
}
diff --git a/fpdfsdk/cpdfsdk_formfillenvironment.h b/fpdfsdk/cpdfsdk_formfillenvironment.h
index c047f22..96e0405 100644
--- a/fpdfsdk/cpdfsdk_formfillenvironment.h
+++ b/fpdfsdk/cpdfsdk_formfillenvironment.h
@@ -95,7 +95,7 @@
void OnSetFieldInputFocus(FPDF_WIDESTRING focusText,
FPDF_DWORD nTextLen,
bool bFocus);
- void DoURIAction(const char* bsURI);
+ void DoURIAction(const char* bsURI, uint32_t modifiers);
void DoGoToAction(int nPageIndex,
int zoomMode,
float* fPosArray,
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index aef5c61..5a589c6 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -3028,23 +3028,36 @@
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);
+ const char kExpectedUri[] = "https://cs.chromium.org/";
+#ifdef PDF_ENABLE_XFA
+ EXPECT_CALL(mock,
+ DoURIActionWithKeyboardModifier(_, StrEq(kExpectedUri), _))
+ .Times(4);
+#else // PDF_ENABLE_XFA
+ EXPECT_CALL(mock, DoURIAction(StrEq(kExpectedUri))).Times(4);
EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(_, _, _)).Times(0);
+#endif // PDF_ENABLE_XFA
}
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));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
modifier = FWL_EVENTFLAG_ControlKey;
- ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
modifier = FWL_EVENTFLAG_ShiftKey;
- ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
modifier |= FWL_EVENTFLAG_ControlKey;
- ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
+
+ ASSERT_FALSE(FORM_OnKeyDown(nullptr, page(), FWL_VKEY_Return, modifier));
+ ASSERT_FALSE(
+ FORM_OnKeyDown(form_handle(), nullptr, FWL_VKEY_Return, modifier));
+ // Following checks should be changed to ASSERT_TRUE if FORM_OnKeyDown starts
+ // handling for Shift/Space/Control.
+ ASSERT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Shift, modifier));
+ ASSERT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Space, modifier));
+ ASSERT_FALSE(
+ FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Control, modifier));
}
class FPDFFormFillActionUriTestVersion2 : public FPDFFormFillActionUriTest {
@@ -3059,26 +3072,34 @@
{
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));
+ const char kExpectedUri[] = "https://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));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
modifier = FWL_EVENTFLAG_ControlKey;
- ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
modifier = FWL_EVENTFLAG_ShiftKey;
- ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
modifier |= FWL_EVENTFLAG_ControlKey;
- ASSERT_FALSE(FORM_OnKeyUp(form_handle(), page(), FWL_VKEY_Return, modifier));
+ ASSERT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Return, modifier));
+
+ ASSERT_FALSE(FORM_OnKeyDown(nullptr, page(), FWL_VKEY_Return, modifier));
+ ASSERT_FALSE(
+ FORM_OnKeyDown(form_handle(), nullptr, FWL_VKEY_Return, modifier));
+ // Following checks should be changed to ASSERT_TRUE if FORM_OnKeyDown starts
+ // handling for Shift/Space/Control.
+ ASSERT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Shift, modifier));
+ ASSERT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Space, modifier));
+ ASSERT_FALSE(
+ FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Control, modifier));
}
diff --git a/testing/resources/annots_action_handling.in b/testing/resources/annots_action_handling.in
index 51becc7..be46954 100644
--- a/testing/resources/annots_action_handling.in
+++ b/testing/resources/annots_action_handling.in
@@ -60,7 +60,7 @@
/A <<
/Type /Action
/S /URI
- /URI (https://www.cs.chromium.org/)
+ /URI (https://cs.chromium.org/)
>>
/F 4
>>
diff --git a/testing/resources/annots_action_handling.pdf b/testing/resources/annots_action_handling.pdf
index 49ea926..92dc830 100644
--- a/testing/resources/annots_action_handling.pdf
+++ b/testing/resources/annots_action_handling.pdf
@@ -61,7 +61,7 @@
/A <<
/Type /Action
/S /URI
- /URI (https://www.cs.chromium.org/)
+ /URI (https://cs.chromium.org/)
>>
/F 4
>>
@@ -81,5 +81,5 @@
/Size 8
>>
startxref
-834
+830
%%EOF
\ No newline at end of file