Commit 417c2602 authored by Victor Fei's avatar Victor Fei Committed by Commit Bot

Implement IA2::get_offsetAtPoint for coord SCREEN_RELATIVE

Implemented IAccessibleText::get_offsetAtPoint for
IA2_COORDTYPE_SCREEN_RELATIVE with associated unittests

get_offsetAtPoint invokes AXPlatformNodeDelegate::
GetInnerTextRangeBoundsRect to retrieve the bounds of each character of
its text. If the input (x, y) in screen coordinate is inside one of the character
bounds, we return the text offset of that character bounds.


Additionally, NVDA also requires this API to be implemented in order
for announcing text content on mouse over to work (crbug.com/1006277).

API doc:
https://accessibility.linuxfoundation.org/a11yspecs/ia2/docs/html/interface_i_accessible_text.html#a9e6bcc4589b93498ecf84d9c8b5326a0

Bug: 996407
Change-Id: I9c91734b2defde495e0f4b64cbcd34be3b5d44c2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1832296Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarKurt Catti-Schmidt <kschmi@microsoft.com>
Commit-Queue: Victor Fei <vicfei@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#703841}
parent f8dbd41f
......@@ -492,20 +492,7 @@ IFACEMETHODIMP BrowserAccessibilityComWin::get_offsetAtPoint(
LONG y,
IA2CoordinateType coord_type,
LONG* offset) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_OFFSET_AT_POINT);
AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes |
ui::AXMode::kInlineTextBoxes);
if (!owner())
return E_FAIL;
if (!offset)
return E_INVALIDARG;
// TODO(dmazzoni): implement this. We're returning S_OK for now so that
// screen readers still return partially accurate results rather than
// completely failing.
*offset = 0;
return S_OK;
return AXPlatformNodeWin::get_offsetAtPoint(x, y, coord_type, offset);
}
IFACEMETHODIMP BrowserAccessibilityComWin::scrollSubstringTo(
......
......@@ -3462,10 +3462,41 @@ IFACEMETHODIMP AXPlatformNodeWin::get_offsetAtPoint(
LONG y,
enum IA2CoordinateType coord_type,
LONG* offset) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_OFFSET_AT_POINT);
COM_OBJECT_VALIDATE_1_ARG(offset);
// We don't support this method, but we have to return something
// rather than E_NOTIMPL or screen readers will complain.
*offset = 0;
*offset = -1;
if (coord_type == IA2CoordinateType::IA2_COORDTYPE_PARENT_RELATIVE) {
// We don't support when the IA2 coordinate type is parent relative, but
// we have to return something rather than E_NOTIMPL or screen readers
// will complain.
NOTIMPLEMENTED_LOG_ONCE() << "See http://crbug.com/1010726";
return S_FALSE;
}
// We currently only handle IA2 screen relative coord type.
DCHECK_EQ(coord_type, IA2_COORDTYPE_SCREEN_RELATIVE);
const AXPlatformNodeWin* hit_child = static_cast<AXPlatformNodeWin*>(
FromNativeViewAccessible(GetDelegate()->HitTestSync(x, y)));
if (!hit_child || !hit_child->IsTextOnlyObject()) {
return S_FALSE;
}
for (int i = 0, text_length = hit_child->GetInnerText().length();
i < text_length; ++i) {
gfx::Rect char_bounds =
hit_child->GetDelegate()->GetInnerTextRangeBoundsRect(
i, i + 1, AXCoordinateSystem::kScreen,
AXClippingBehavior::kUnclipped);
if (char_bounds.Contains(x, y)) {
*offset = i;
break;
}
}
return S_OK;
}
......
......@@ -2878,6 +2878,154 @@ TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextGetNCharacters) {
EXPECT_EQ(4, count);
}
TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextGetOffsetAtPoint) {
AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kWebArea;
root.relative_bounds.bounds = gfx::RectF(0, 0, 300, 200);
root.child_ids = {2, 3};
AXNodeData button;
button.id = 2;
button.role = ax::mojom::Role::kButton;
button.SetName("button");
button.relative_bounds.bounds = gfx::RectF(20, 0, 10, 10);
AXNodeData static_text1;
static_text1.id = 3;
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.SetName("line 1");
static_text1.relative_bounds.bounds = gfx::RectF(0, 20, 30, 10);
static_text1.child_ids = {4};
AXNodeData inline_box1;
inline_box1.id = 4;
inline_box1.role = ax::mojom::Role::kInlineTextBox;
inline_box1.SetName("line 1");
inline_box1.relative_bounds.bounds = gfx::RectF(0, 20, 30, 10);
std::vector<int32_t> character_offsets1;
// The width of each character is 5px, height is 10px.
character_offsets1.push_back(5); // "l" {0, 20, 5x10}
character_offsets1.push_back(10); // "i" {5, 20, 5x10}
character_offsets1.push_back(15); // "n" {10, 20, 5x10}
character_offsets1.push_back(20); // "e" {15, 20, 5x10}
character_offsets1.push_back(25); // " " {20, 20, 5x10}
character_offsets1.push_back(30); // "1" {25, 20, 5x10}
inline_box1.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 5});
inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{4, 6});
Init(root, button, static_text1, inline_box1);
LONG offset_result;
ComPtr<IAccessible> root_iaccessible(IAccessibleFromNode(GetRootNode()));
ASSERT_NE(nullptr, root_iaccessible.Get());
ComPtr<IAccessibleText> root_text;
root_iaccessible.As(&root_text);
ASSERT_NE(nullptr, root_text.Get());
// Test point(0, 0) with coordinate parent relative, which is not supported.
// Expected result: S_FALSE.
EXPECT_EQ(S_FALSE, root_text->get_offsetAtPoint(
0, 0, IA2CoordinateType::IA2_COORDTYPE_PARENT_RELATIVE,
&offset_result));
EXPECT_EQ(-1, offset_result);
// Test point(0, 0) retrieved from IAccessibleText of the root web area is
// outside of any text. Expected result: S_FALSE.
EXPECT_EQ(S_FALSE, root_text->get_offsetAtPoint(
0, 0, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(-1, offset_result);
// Test point(25, 5) retrieved from IAccessibleText of the root web area is
// on button, and outside of any text. Expected result: S_FALSE.
EXPECT_EQ(S_FALSE,
root_text->get_offsetAtPoint(
25, 5, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(-1, offset_result);
// Test point(0, 20) retrieved from IAccessibleText of the root web area is
// on "line 1", character="l". Expected result: S_OK, offset=0.
EXPECT_EQ(S_OK, root_text->get_offsetAtPoint(
0, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(0, offset_result);
AXNode* static_text1_node = GetRootNode()->children()[1];
ComPtr<IAccessible> text1_iaccessible(IAccessibleFromNode(static_text1_node));
ASSERT_NE(nullptr, text1_iaccessible.Get());
ComPtr<IAccessibleText> text1;
text1_iaccessible.As(&text1);
ASSERT_NE(nullptr, text1.Get());
// "l" 4 points of bounds {(0, 20), (5, 20), (0, 30), (5, 30)}
// "i" 4 points of bounds {(5, 20), (10, 20), (5, 30), (10, 30)}
// "n" 4 points of bounds {(10, 20), (15, 20), (10, 30), (15, 30)}
// "e" 4 points of bounds {(15, 20), (20, 20), (15, 30), (20, 30)}
// " " 4 points of bounds {(20, 20), (25, 20), (20, 30), (25, 30)}
// "1" 4 points of bounds {(25, 20), (30, 20), (25, 30), (30, 30)}
// Test point(0, 0) outside of any character bounds and text.
EXPECT_EQ(S_FALSE, text1->get_offsetAtPoint(
0, 0, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(-1, offset_result);
// Test point(30, 30) outside of any character bounds but on the text.
EXPECT_EQ(S_OK, text1->get_offsetAtPoint(
30, 30, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(-1, offset_result);
// Test point(0, 20) inside bounds of "l", text offset=0
// character bounds={(0, 20), (5, 20), (0, 30), (5, 30)}
EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
0, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE, &offset_result));
EXPECT_EQ(0, offset_result);
// Test point(9, 20) inside bounds of "i", text offset=1
// character bounds={(5, 20), (10, 20), (5, 30), (10, 30)}
EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
9, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE, &offset_result));
EXPECT_EQ(1, offset_result);
// Test point(10, 30) inside bounds of "n", text offset=2
// character bounds={(10, 20), (15, 20), (10, 30), (15, 30)}
EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
10, 29, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(2, offset_result);
// Test point(19, 29) inside bounds of "e", text offset=3
// character bounds={(15, 20), (20, 20), (15, 30), (20, 30)
EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
19, 29, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(3, offset_result);
// Test point(23, 25) inside bounds of " ", text offset=4
// character bounds={(20, 20), (25, 20), (20, 30), (25, 30)}
EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
23, 25, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(4, offset_result);
// Test point(25, 20) inside bounds of "1", text offset=5
// character bounds={(25, 20), (30, 20), (25, 30), (30, 30)}
EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
25, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
&offset_result));
EXPECT_EQ(5, offset_result);
}
TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextTextFieldRemoveSelection) {
Init(BuildTextFieldWithSelectionRange(1, 2));
......
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