Allow focus to move across pages on tab events
This CL adds tab handling in PDF. A tab event now allows the user to move from the last annotation on a page to first annotation on the next page. Unit tests have been added to validate the scenario. Bug: 989040 Change-Id: I46106e88946bdc3e667c9897cbcba3ff418aecfc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2061634 Commit-Queue: Ankit Kumar 🌪️ <ankk@microsoft.com> Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: Kevin Babbitt <kbabbitt@microsoft.com> Cr-Commit-Position: refs/heads/master@{#758074}
This commit is contained in:
@ -37,6 +37,7 @@
|
||||
#include "pdf/pdfium/pdfium_permissions.h"
|
||||
#include "pdf/pdfium/pdfium_unsupported_features.h"
|
||||
#include "pdf/url_loader_wrapper_impl.h"
|
||||
#include "ppapi/c/ppb_input_event.h"
|
||||
#include "ppapi/cpp/instance.h"
|
||||
#include "ppapi/cpp/private/pdf.h"
|
||||
#include "ppapi/cpp/var_dictionary.h"
|
||||
@ -46,6 +47,7 @@
|
||||
#include "third_party/pdfium/public/fpdf_attachment.h"
|
||||
#include "third_party/pdfium/public/fpdf_catalog.h"
|
||||
#include "third_party/pdfium/public/fpdf_ext.h"
|
||||
#include "third_party/pdfium/public/fpdf_fwlevent.h"
|
||||
#include "third_party/pdfium/public/fpdf_ppo.h"
|
||||
#include "third_party/pdfium/public/fpdf_searchex.h"
|
||||
#include "ui/events/keycodes/keyboard_codes.h"
|
||||
@ -1056,6 +1058,7 @@ bool PDFiumEngine::OnLeftMouseDown(const pp::MouseInputEvent& event) {
|
||||
return true;
|
||||
|
||||
if (page_index != -1) {
|
||||
focus_item_type_ = FocusElementType::kPage;
|
||||
last_focused_page_ = page_index;
|
||||
double page_x;
|
||||
double page_y;
|
||||
@ -1479,6 +1482,11 @@ bool PDFiumEngine::ExtendSelection(int page_index, int char_index) {
|
||||
}
|
||||
|
||||
bool PDFiumEngine::OnKeyDown(const pp::KeyboardInputEvent& event) {
|
||||
// Handle tab events first as we might need to transition focus to an
|
||||
// annotation in PDF.
|
||||
if (event.GetKeyCode() == FWL_VKEY_Tab)
|
||||
return HandleTabEvent(event.GetModifiers());
|
||||
|
||||
if (last_focused_page_ == -1)
|
||||
return false;
|
||||
|
||||
@ -2689,12 +2697,6 @@ void PDFiumEngine::CalculateVisiblePages() {
|
||||
} else {
|
||||
pages_[i]->Unload();
|
||||
}
|
||||
|
||||
// If the last focused page was a page that's no longer visible, reset
|
||||
// that variable so that we don't send keyboard events to it (the focus
|
||||
// will be lost when the page is first closed anyways).
|
||||
if (static_cast<int>(i) == last_focused_page_)
|
||||
last_focused_page_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3753,6 +3755,109 @@ PdfVersion PDFiumEngine::GetDocumentVersion() const {
|
||||
}
|
||||
}
|
||||
|
||||
bool PDFiumEngine::HandleTabEvent(uint32_t modifiers) {
|
||||
bool alt_key = !!(modifiers & PP_INPUTEVENT_MODIFIER_ALTKEY);
|
||||
bool ctrl_key = !!(modifiers & PP_INPUTEVENT_MODIFIER_CONTROLKEY);
|
||||
if (alt_key || ctrl_key)
|
||||
return HandleTabEventWithModifiers(modifiers);
|
||||
|
||||
return modifiers & PP_INPUTEVENT_MODIFIER_SHIFTKEY
|
||||
? HandleTabBackward(modifiers)
|
||||
: HandleTabForward(modifiers);
|
||||
}
|
||||
|
||||
bool PDFiumEngine::HandleTabEventWithModifiers(uint32_t modifiers) {
|
||||
// Only handle cases when a page is focused, else return false.
|
||||
switch (focus_item_type_) {
|
||||
case FocusElementType::kNone:
|
||||
case FocusElementType::kDocument:
|
||||
return false;
|
||||
case FocusElementType::kPage:
|
||||
if (last_focused_page_ == -1)
|
||||
return false;
|
||||
return !!FORM_OnKeyDown(form(), pages_[last_focused_page_]->GetPage(),
|
||||
FWL_VKEY_Tab, modifiers);
|
||||
default:
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PDFiumEngine::HandleTabForward(uint32_t modifiers) {
|
||||
if (focus_item_type_ == FocusElementType::kNone) {
|
||||
focus_item_type_ = FocusElementType::kDocument;
|
||||
return true;
|
||||
}
|
||||
|
||||
int page_index = last_focused_page_;
|
||||
if (page_index == -1)
|
||||
page_index = 0;
|
||||
|
||||
bool did_tab_forward = false;
|
||||
while (!did_tab_forward && PageIndexInBounds(page_index)) {
|
||||
did_tab_forward = !!FORM_OnKeyDown(form(), pages_[page_index]->GetPage(),
|
||||
FWL_VKEY_Tab, modifiers);
|
||||
if (!did_tab_forward)
|
||||
++page_index;
|
||||
}
|
||||
|
||||
if (did_tab_forward) {
|
||||
last_focused_page_ = page_index;
|
||||
focus_item_type_ = FocusElementType::kPage;
|
||||
} else {
|
||||
last_focused_page_ = -1;
|
||||
focus_item_type_ = FocusElementType::kNone;
|
||||
}
|
||||
return did_tab_forward;
|
||||
}
|
||||
|
||||
bool PDFiumEngine::HandleTabBackward(uint32_t modifiers) {
|
||||
if (focus_item_type_ == FocusElementType::kDocument) {
|
||||
focus_item_type_ = FocusElementType::kNone;
|
||||
return false;
|
||||
}
|
||||
|
||||
int page_index = last_focused_page_;
|
||||
if (page_index == -1)
|
||||
page_index = GetNumberOfPages() - 1;
|
||||
|
||||
bool did_tab_backward = false;
|
||||
while (!did_tab_backward && PageIndexInBounds(page_index)) {
|
||||
did_tab_backward = !!FORM_OnKeyDown(form(), pages_[page_index]->GetPage(),
|
||||
FWL_VKEY_Tab, modifiers);
|
||||
if (!did_tab_backward)
|
||||
--page_index;
|
||||
}
|
||||
|
||||
if (did_tab_backward) {
|
||||
last_focused_page_ = page_index;
|
||||
focus_item_type_ = FocusElementType::kPage;
|
||||
} else {
|
||||
// No focusable annotation found in pages. Possible scenarios:
|
||||
// Case 1: |focus_item_type_| is None. Since no object in any page can take
|
||||
// the focus, the document should take focus.
|
||||
// Case 2: |focus_item_type_| is Page. Since there aren't any objects that
|
||||
// could take focus, the document should take focus.
|
||||
// Case 3: |focus_item_type_| is Document. Move focus_item_type_ to None.
|
||||
switch (focus_item_type_) {
|
||||
case FocusElementType::kPage:
|
||||
case FocusElementType::kNone:
|
||||
did_tab_backward = true;
|
||||
last_focused_page_ = -1;
|
||||
focus_item_type_ = FocusElementType::kDocument;
|
||||
KillFormFocus();
|
||||
break;
|
||||
case FocusElementType::kDocument:
|
||||
focus_item_type_ = FocusElementType::kNone;
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return did_tab_backward;
|
||||
}
|
||||
|
||||
#if defined(PDF_ENABLE_XFA)
|
||||
void PDFiumEngine::UpdatePageCount() {
|
||||
InvalidateAllPages();
|
||||
|
@ -162,6 +162,11 @@ class PDFiumEngine : public PDFEngine,
|
||||
FPDF_DOCUMENT doc() const;
|
||||
FPDF_FORMHANDLE form() const;
|
||||
|
||||
// State transition when tabbing forward:
|
||||
// None -> Document -> Page -> None (when focusable annotations on all pages
|
||||
// are done).
|
||||
enum class FocusElementType { kNone, kDocument, kPage };
|
||||
|
||||
private:
|
||||
// This helper class is used to detect the difference in selection between
|
||||
// construction and destruction. At destruction, it invalidates all the
|
||||
@ -209,6 +214,7 @@ class PDFiumEngine : public PDFEngine,
|
||||
};
|
||||
|
||||
friend class FormFillerTest;
|
||||
friend class PDFiumEngineTabbingTest;
|
||||
friend class PDFiumFormFiller;
|
||||
friend class PDFiumTestBase;
|
||||
friend class SelectionChangeInvalidator;
|
||||
@ -580,6 +586,15 @@ class PDFiumEngine : public PDFEngine,
|
||||
// Retrieves the version of the PDF (e.g. 1.4 or 2.0) as an enum.
|
||||
PdfVersion GetDocumentVersion() const;
|
||||
|
||||
// This is a layer between OnKeyDown() and actual tab handling to facilitate
|
||||
// testing.
|
||||
bool HandleTabEvent(uint32_t modifiers);
|
||||
|
||||
// Helper functions to handle tab events.
|
||||
bool HandleTabEventWithModifiers(uint32_t modifiers);
|
||||
bool HandleTabForward(uint32_t modifiers);
|
||||
bool HandleTabBackward(uint32_t modifiers);
|
||||
|
||||
PDFEngine::Client* const client_;
|
||||
|
||||
// The current document layout.
|
||||
@ -681,6 +696,9 @@ class PDFiumEngine : public PDFEngine,
|
||||
// Timer for touch long press detection.
|
||||
base::OneShotTimer touch_timer_;
|
||||
|
||||
// The focus item type for the currently focused object.
|
||||
FocusElementType focus_item_type_ = FocusElementType::kNone;
|
||||
|
||||
// Holds the zero-based page index of the last page that had the focused
|
||||
// object.
|
||||
int last_focused_page_ = -1;
|
||||
|
@ -4,12 +4,14 @@
|
||||
|
||||
#include "pdf/pdfium/pdfium_engine.h"
|
||||
|
||||
#include "base/test/task_environment.h"
|
||||
#include "pdf/document_layout.h"
|
||||
#include "pdf/document_metadata.h"
|
||||
#include "pdf/pdfium/pdfium_page.h"
|
||||
#include "pdf/pdfium/pdfium_test_base.h"
|
||||
#include "pdf/test/test_client.h"
|
||||
#include "pdf/test/test_utils.h"
|
||||
#include "ppapi/c/ppb_input_event.h"
|
||||
#include "ppapi/cpp/size.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
@ -222,4 +224,203 @@ TEST_F(PDFiumEngineTest, GetBadPdfVersion) {
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class PDFiumEngineTabbingTest : public PDFiumTestBase {
|
||||
public:
|
||||
PDFiumEngineTabbingTest() = default;
|
||||
~PDFiumEngineTabbingTest() override = default;
|
||||
PDFiumEngineTabbingTest(const PDFiumEngineTabbingTest&) = delete;
|
||||
PDFiumEngineTabbingTest& operator=(const PDFiumEngineTabbingTest&) = delete;
|
||||
|
||||
bool HandleTabEvent(PDFiumEngine* engine, uint32_t modifiers) {
|
||||
return engine->HandleTabEvent(modifiers);
|
||||
}
|
||||
|
||||
PDFiumEngine::FocusElementType GetFocusedElementType(PDFiumEngine* engine) {
|
||||
return engine->focus_item_type_;
|
||||
}
|
||||
|
||||
int GetLastFocusedPage(PDFiumEngine* engine) {
|
||||
return engine->last_focused_page_;
|
||||
}
|
||||
|
||||
protected:
|
||||
base::test::TaskEnvironment task_environment_{
|
||||
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
|
||||
};
|
||||
|
||||
TEST_F(PDFiumEngineTabbingTest, TabbingForwardTest) {
|
||||
/*
|
||||
* Document structure
|
||||
* Document
|
||||
* ++ Page 1
|
||||
* ++++ Annotation
|
||||
* ++++ Annotation
|
||||
* ++ Page 2
|
||||
* ++++ Annotation
|
||||
*/
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
|
||||
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
|
||||
ASSERT_EQ(2, engine->GetNumberOfPages());
|
||||
|
||||
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
}
|
||||
|
||||
TEST_F(PDFiumEngineTabbingTest, TabbingBackwardTest) {
|
||||
/*
|
||||
* Document structure
|
||||
* Document
|
||||
* ++ Page 1
|
||||
* ++++ Annotation
|
||||
* ++++ Annotation
|
||||
* ++ Page 2
|
||||
* ++++ Annotation
|
||||
*/
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
|
||||
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
|
||||
ASSERT_EQ(2, engine->GetNumberOfPages());
|
||||
|
||||
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(1, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(0, GetLastFocusedPage(engine.get()));
|
||||
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
}
|
||||
|
||||
TEST_F(PDFiumEngineTabbingTest, TabbingWithModifiers) {
|
||||
/*
|
||||
* Document structure
|
||||
* Document
|
||||
* ++ Page 1
|
||||
* ++++ Annotation
|
||||
* ++++ Annotation
|
||||
* ++ Page 2
|
||||
* ++++ Annotation
|
||||
*/
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
|
||||
&client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
|
||||
ASSERT_EQ(2, engine->GetNumberOfPages());
|
||||
|
||||
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
|
||||
|
||||
// Tabbing with ctrl modifier.
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_CONTROLKEY));
|
||||
// Tabbing with alt modifier.
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_ALTKEY));
|
||||
|
||||
// Tab to bring document into focus.
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
// Tabbing with ctrl modifier.
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_CONTROLKEY));
|
||||
// Tabbing with alt modifier.
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_ALTKEY));
|
||||
|
||||
// Tab to bring first page into focus.
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kPage,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
// Tabbing with ctrl modifier.
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_CONTROLKEY));
|
||||
// Tabbing with alt modifier.
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_ALTKEY));
|
||||
}
|
||||
|
||||
TEST_F(PDFiumEngineTabbingTest, NoFocusableItemTabbingTest) {
|
||||
/*
|
||||
* Document structure
|
||||
* Document
|
||||
* ++ Page 1
|
||||
* ++ Page 2
|
||||
*/
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine =
|
||||
InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
|
||||
ASSERT_EQ(2, engine->GetNumberOfPages());
|
||||
|
||||
ASSERT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
EXPECT_EQ(-1, GetLastFocusedPage(engine.get()));
|
||||
|
||||
// Tabbing forward.
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), 0));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
// Tabbing backward.
|
||||
ASSERT_TRUE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kDocument,
|
||||
GetFocusedElementType(engine.get()));
|
||||
|
||||
ASSERT_FALSE(HandleTabEvent(engine.get(), PP_INPUTEVENT_MODIFIER_SHIFTKEY));
|
||||
EXPECT_EQ(PDFiumEngine::FocusElementType::kNone,
|
||||
GetFocusedElementType(engine.get()));
|
||||
}
|
||||
|
||||
} // namespace chrome_pdf
|
||||
|
Reference in New Issue
Block a user