Commit be453988 authored by Martin Robinson's avatar Martin Robinson Committed by Commit Bot

Return an empty ATK selection for unfocused text inputs

Instead of returning the previous text selection extents for unfocused
text inputs in the ATK API, return an empty selection which matches what
the visual selection shows.

Bug: 1015354
Change-Id: I767f44dc52df3e4de579f6c4e6a027d72630d60a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1886811
Commit-Queue: Martin Robinson <mrobinson@igalia.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#715237}
parent 53b39d4c
......@@ -30,6 +30,13 @@ AtkObject* FindAtkObjectParentFrame(AtkObject* atk_object) {
return nullptr;
}
static bool IsAtkObjectFocused(AtkObject* object) {
AtkStateSet* state_set = atk_object_ref_state_set(object);
bool result = atk_state_set_contains_state(state_set, ATK_STATE_FOCUSED);
g_object_unref(state_set);
return result;
}
} // namespace
class AccessibilityAuraLinuxBrowserTest : public AccessibilityBrowserTest {
......@@ -1011,13 +1018,6 @@ IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
IN_PROC_BROWSER_TEST_F(
AccessibilityAuraLinuxBrowserTest,
DISABLED_TestSetCaretSetsSequentialFocusNavigationStartingPoint) {
auto is_focused = [](AtkObject* object) {
AtkStateSet* state_set = atk_object_ref_state_set(object);
bool result = atk_state_set_contains_state(state_set, ATK_STATE_FOCUSED);
g_object_unref(state_set);
return result;
};
LoadInitialAccessibilityTreeFromHtml(
R"HTML(<!DOCTYPE html>
<html>
......@@ -1065,7 +1065,7 @@ IN_PROC_BROWSER_TEST_F(
SimulateKeyPress(shell()->web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
ui::VKEY_TAB, false, false, false, false);
waiter->WaitForNotification();
ASSERT_TRUE(is_focused(child_3));
ASSERT_TRUE(IsAtkObjectFocused(child_3));
// Now we repeat a similar test, but this time setting the caret offset on
// the document. In this case, the sequential navigation starting point
......@@ -1079,7 +1079,7 @@ IN_PROC_BROWSER_TEST_F(
ui::VKEY_TAB, false, false, false, false);
waiter->WaitForNotification();
ASSERT_TRUE(is_focused(child_7));
ASSERT_TRUE(IsAtkObjectFocused(child_7));
// Now test setting the caret in a node that can accept focus. That
// node should actually receive focus.
......@@ -1087,7 +1087,7 @@ IN_PROC_BROWSER_TEST_F(
SimulateKeyPress(shell()->web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
ui::VKEY_TAB, false, false, false, false);
waiter->WaitForNotification();
ASSERT_TRUE(is_focused(child_3));
ASSERT_TRUE(IsAtkObjectFocused(child_3));
AtkObject* link_section = atk_object_ref_accessible_child(child_7, 0);
EXPECT_NE(link_section, nullptr);
......@@ -1095,7 +1095,7 @@ IN_PROC_BROWSER_TEST_F(
EXPECT_NE(link_text, nullptr);
atk_text_set_caret_offset(ATK_TEXT(link_text), 0);
waiter->WaitForNotification();
ASSERT_TRUE(is_focused(child_7));
ASSERT_TRUE(IsAtkObjectFocused(child_7));
g_object_unref(link_section);
g_object_unref(link_text);
......@@ -1104,6 +1104,89 @@ IN_PROC_BROWSER_TEST_F(
g_object_unref(child_7);
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestFocusInputTextFields) {
auto verify_selection = [](AtkObject* object, const char* selection) {
gchar* selected_text =
atk_text_get_selection(ATK_TEXT(object), 0, nullptr, nullptr);
EXPECT_STREQ(selected_text, selection);
g_free(selected_text);
int n_selections = atk_text_get_n_selections(ATK_TEXT(object));
EXPECT_EQ(n_selections, selection ? 1 : 0);
};
LoadInitialAccessibilityTreeFromHtml(
R"HTML(<!DOCTYPE html>
<html>
<body>
<div>
<input value="First Field">
<input value="Second Field">
<input value="Third Field">
<input value="Fourth Field">
</div>
</body>
</html>)HTML");
// Retrieve the AtkObject interface for the document node.
AtkObject* document = GetRendererAccessible();
ASSERT_TRUE(ATK_IS_COMPONENT(document));
AtkObject* container = atk_object_ref_accessible_child(document, 0);
AtkObject* field_1 = atk_object_ref_accessible_child(container, 0);
AtkObject* field_2 = atk_object_ref_accessible_child(container, 1);
AtkObject* field_3 = atk_object_ref_accessible_child(container, 2);
AtkObject* field_4 = atk_object_ref_accessible_child(container, 3);
EXPECT_NE(field_1, nullptr);
EXPECT_NE(field_2, nullptr);
EXPECT_NE(field_3, nullptr);
EXPECT_NE(field_4, nullptr);
auto waiter = std::make_unique<AccessibilityNotificationWaiter>(
shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kFocus);
atk_component_grab_focus(ATK_COMPONENT(field_1));
waiter->WaitForNotification();
waiter = std::make_unique<AccessibilityNotificationWaiter>(
shell()->web_contents(), ui::kAXModeComplete,
ax::mojom::Event::kTextSelectionChanged);
EXPECT_TRUE(atk_text_set_selection(ATK_TEXT(field_1), 0, 0, 5));
waiter->WaitForNotification();
EXPECT_TRUE(atk_text_set_selection(ATK_TEXT(field_2), 0, 0, -1));
waiter->WaitForNotification();
// Only the field that is currently focused should return a selection.
ASSERT_FALSE(IsAtkObjectFocused(field_1));
ASSERT_TRUE(IsAtkObjectFocused(field_2));
verify_selection(field_1, nullptr);
verify_selection(field_2, "Second Field");
verify_selection(field_3, nullptr);
verify_selection(field_4, nullptr);
waiter = std::make_unique<AccessibilityNotificationWaiter>(
shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kFocus);
atk_component_grab_focus(ATK_COMPONENT(field_1));
waiter->WaitForNotification();
// Now that the focus has returned to the first field, it should return the
// original selection that we set on it.
ASSERT_TRUE(IsAtkObjectFocused(field_1));
ASSERT_FALSE(IsAtkObjectFocused(field_2));
verify_selection(field_1, "First");
verify_selection(field_2, nullptr);
verify_selection(field_3, nullptr);
verify_selection(field_4, nullptr);
g_object_unref(field_1);
g_object_unref(field_2);
g_object_unref(field_3);
g_object_unref(field_4);
g_object_unref(container);
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestTextEventsInStaticText) {
LoadInitialAccessibilityTreeFromHtml(std::string(
......
[document web] focusable focused
++[section]
++++[entry] editable focusable selectable-text text-input-type:email caret_offset=0
++++[entry] editable focusable selectable-text text-input-type:email
[document web] focusable focused
++[section]
++++[entry] editable focusable selectable-text text-input-type:search caret_offset=0
++++[entry] editable focusable selectable-text text-input-type:search
[document web] focusable focused
++[section]
++++[entry] editable focusable selectable-text text-input-type:tel caret_offset=0
++++[entry] editable focusable selectable-text text-input-type:tel
[document web] focusable focused
++[section]
++++[entry] editable focusable selectable-text text-input-type:url caret_offset=0
++++[entry] editable focusable selectable-text text-input-type:url
[document web] focusable focused
++[section]
++++[entry] editable focusable multi-line selectable-text caret_offset=0
++++[entry] editable focusable multi-line selectable-text
[document web] focusable focused
++[section]
++++[entry] focusable multi-line selectable-text caret_offset=0
++++[entry] focusable multi-line selectable-text
......@@ -3317,8 +3317,6 @@ void AXPlatformNodeAuraLinux::EmitSelectionChangedSignal(bool had_selection) {
AtkObject* atk_object = GetOrCreateAtkObject();
DCHECK(ATK_IS_TEXT(atk_object));
std::pair<int, int> selection;
GetSelectionOffsets(&selection.first, &selection.second);
// ATK does not consider a collapsed selection a selection, so
// when the collapsed selection changes (caret movement), we should
......@@ -3335,8 +3333,7 @@ void AXPlatformNodeAuraLinux::EmitCaretChangedSignal() {
}
DCHECK(HasCaret());
std::pair<int, int> selection;
GetSelectionOffsets(&selection.first, &selection.second);
std::pair<int, int> selection = GetSelectionOffsetsForAtk();
AtkObject* atk_object = GetOrCreateAtkObject();
DCHECK(ATK_IS_TEXT(atk_object));
......@@ -4023,9 +4020,8 @@ int AXPlatformNodeAuraLinux::GetCaretOffset() {
return -1;
}
int selection_start, selection_end;
GetSelectionOffsets(&selection_start, &selection_end);
return UTF16ToUnicodeOffsetInText(selection_end);
std::pair<int, int> selection = GetSelectionOffsetsForAtk();
return UTF16ToUnicodeOffsetInText(selection.second);
}
bool AXPlatformNodeAuraLinux::SetCaretOffset(int offset) {
......@@ -4064,9 +4060,8 @@ bool AXPlatformNodeAuraLinux::SetTextSelectionForAtkText(int start_offset,
// Even if we don't change anything, we still want to act like we
// were successful.
int old_start_offset, old_end_offset;
GetSelectionExtents(&old_start_offset, &old_end_offset);
if (old_start_offset == start_offset && old_end_offset == end_offset)
std::pair<int, int> old_offsets = GetSelectionOffsetsForAtk();
if (old_offsets.first == start_offset && old_offsets.second == end_offset)
return true;
if (!SetHypertextSelection(start_offset, end_offset))
......@@ -4076,8 +4071,7 @@ bool AXPlatformNodeAuraLinux::SetTextSelectionForAtkText(int start_offset,
}
bool AXPlatformNodeAuraLinux::HasSelection() {
std::pair<int, int> selection;
GetSelectionOffsets(&selection.first, &selection.second);
std::pair<int, int> selection = GetSelectionOffsetsForAtk();
return selection.first >= 0 && selection.second >= 0 &&
selection.first != selection.second;
}
......@@ -4089,26 +4083,25 @@ void AXPlatformNodeAuraLinux::GetSelectionExtents(int* start_offset,
if (end_offset)
*end_offset = 0;
int selection_start, selection_end;
GetSelectionOffsets(&selection_start, &selection_end);
if (selection_start < 0 || selection_end < 0 ||
selection_start == selection_end)
std::pair<int, int> selection = GetSelectionOffsetsForAtk();
if (selection.first < 0 || selection.second < 0 ||
selection.first == selection.second)
return;
// We should ignore the direction of the selection when exposing start and
// end offsets. According to the ATK documentation the end offset is always
// the offset immediately past the end of the selection. This wouldn't make
// sense if end < start.
if (selection_end < selection_start)
std::swap(selection_start, selection_end);
if (selection.second < selection.first)
std::swap(selection.first, selection.second);
selection_start = UTF16ToUnicodeOffsetInText(selection_start);
selection_end = UTF16ToUnicodeOffsetInText(selection_end);
selection.first = UTF16ToUnicodeOffsetInText(selection.first);
selection.second = UTF16ToUnicodeOffsetInText(selection.second);
if (start_offset)
*start_offset = selection_start;
*start_offset = selection.first;
if (end_offset)
*end_offset = selection_end;
*end_offset = selection.second;
}
// Since this method doesn't return a static gchar*, we expect the caller of
......@@ -4403,4 +4396,18 @@ gfx::Point AXPlatformNodeAuraLinux::ConvertPointToScreenCoordinates(
}
}
std::pair<int, int> AXPlatformNodeAuraLinux::GetSelectionOffsetsForAtk() {
// In web content we always want to look at the selection from the tree
// instead of the selection that might be set via node attributes. This is
// because the tree selection is the absolute truth about what is visually
// selected, whereas node attributes might contain selection extents that are
// no longer part of the visual selection.
std::pair<int, int> selection;
if (GetDelegate()->IsWebContent())
GetSelectionOffsetsFromTree(&selection.first, &selection.second);
else
GetSelectionOffsets(&selection.first, &selection.second);
return selection;
}
} // namespace ui
......@@ -224,6 +224,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// return it, otherwise return base::nullopt;
base::Optional<FindInPageResultInfo> GetSelectionOffsetsFromFindInPage();
std::pair<int, int> GetSelectionOffsetsForAtk();
// Get the embedded object ("hyperlink") indices for this object in the
// parent. If this object doesn't have a parent or isn't embedded, return
// nullopt.
......
......@@ -1470,6 +1470,13 @@ void AXPlatformNodeBase::GetSelectionOffsets(int* selection_start,
return;
}
GetSelectionOffsetsFromTree(selection_start, selection_end);
}
void AXPlatformNodeBase::GetSelectionOffsetsFromTree(int* selection_start,
int* selection_end) {
DCHECK(selection_start && selection_end);
*selection_start = GetUnignoredSelectionAnchor();
*selection_end = GetUnignoredSelectionFocus();
if (*selection_start < 0 || *selection_end < 0)
......
......@@ -376,6 +376,7 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// The greatest of the two offsets is one past the last character of the
// selection.)
void GetSelectionOffsets(int* selection_start, int* selection_end);
void GetSelectionOffsetsFromTree(int* selection_start, int* selection_end);
// Returns the hyperlink at the given text position, or nullptr if no
// hyperlink can be found.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment