Commit 93769a49 authored by Samuel Huang's avatar Samuel Huang Committed by Commit Bot

[Accessibility] Make shadow DOM Elements available to hit tests.

crrev.com/c/2051870 added DOM retargeting usage to
AXLayoutObject::AccessibilityHitTest() so that accessibility hit test
targets an <input> Element instead of its internals. However, the fix
was too aggressive, and caused hit tests to miss Elements inside all
shadow DOM, including those in custom elements (disregarding whether
mode is "open" and "closed", and ignoring non-Element Nodes).

This CL removes DOM retarget usage for accessibility hit test. Instead,
when a Node in a shadow DOM is targeted, if the Node is in a user-agent
shadow tree then the target is changed to the shadow root's host.

Bug: 1111800
Change-Id: I8bcfaea0a113166464efe82f14f986c1bfa0c2c7
Fixed: 1111800
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2495660Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Samuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821258}
parent 8f1af89e
......@@ -1479,14 +1479,18 @@ AXObject* AXLayoutObject::AccessibilityHitTest(const IntPoint& point) const {
return nullptr;
}
LayoutObject* obj = node->GetLayoutObject();
// Retarget to respect https://dom.spec.whatwg.org/#retarget.
if (auto* elem = DynamicTo<Element>(node)) {
Element* element = &(GetDocument()->Retarget(*elem));
obj = element->GetLayoutObject();
// If |node| is in a user-agent shadow tree, reassign it as the host to hide
// details in the shadow tree. Previously this was implemented by using
// Retargeting (https://dom.spec.whatwg.org/#retarget), but this caused
// elements inside regular shadow DOMs to be ignored by screen reader. See
// crbug.com/1111800 and crbug.com/1048959.
const TreeScope& tree_scope = node->GetTreeScope();
if (auto* shadow_root = DynamicTo<ShadowRoot>(tree_scope.RootNode())) {
if (shadow_root->IsUserAgent())
node = &shadow_root->host();
}
LayoutObject* obj = node->GetLayoutObject();
if (!obj)
return nullptr;
......
......@@ -96,21 +96,22 @@ TEST_F(AXLayoutObjectTest, StringValueTextSecurity) {
ax_select->StringValue().Utf8());
}
// Test if AX takes 'Retarget' described from
// https://dom.spec.whatwg.org/#retarget after hit-testing.
// Test AX hit test for user-agent shadow DOM, which should ignore the shadow
// Node at the given point, and select the host Element instead.
TEST_F(AXLayoutObjectTest, AccessibilityHitTest) {
SetBodyInnerHTML(
"<style>\
.A{display:flex;flex:100%;margin-top:-37px;height:34px}\
.B{display:flex;flex:1;flex-wrap:wrap}\
.C{flex:100%;height:34px}\
</style>\
<div class='B'>\
<div class='C'></div>\
<input class='A' aria-label='Search' role='combobox'>\
</div>");
"<style>"
" .A{display:flex;flex:100%;margin-top:-37px;height:34px}"
" .B{display:flex;flex:1;flex-wrap:wrap}"
" .C{flex:100%;height:34px}"
"</style>"
"<div class='B'>"
"<div class='C'></div>"
"<input class='A' aria-label='Search' role='combobox'>"
"</div>");
const AXObject* ax_root = GetAXRootObject();
ASSERT_NE(nullptr, ax_root);
// (8, 5) initially hits the editable DIV inside <input>.
const IntPoint position(8, 5);
AXObject* hit_test_result = ax_root->AccessibilityHitTest(position);
EXPECT_NE(nullptr, hit_test_result);
......@@ -118,4 +119,51 @@ TEST_F(AXLayoutObjectTest, AccessibilityHitTest) {
ax::mojom::Role::kTextFieldWithComboBox);
}
// Tests AX hit test for open / closed shadow DOM, which should select the
// shadow Node under the given point (as opposed to taking the host Element,
// which is the case for user-agent shadow DOM).
TEST_F(AXLayoutObjectTest, AccessibilityHitTestShadowDOM) {
auto run_test = [&](ShadowRootType root_type) {
SetBodyInnerHTML(
"<style>"
"#host_a{position:absolute;}"
"</style>"
"<div id='host_a'>"
"</div>");
auto* host_a = GetElementById("host_a");
auto& shadow_a = host_a->AttachShadowRootInternal(root_type);
shadow_a.setInnerHTML(
"<style>"
"label {"
" display: inline-block;"
" height: 100px;"
" width: 100px;"
"}"
"input {"
" appearance: none;"
" height: 0;"
" width: 0;"
"}"
"</style>"
"<label id='label1' role='radio'>"
" <input type='radio' name='radio-main'>"
"</label>"
"<label id='label2' role='radio'>"
" <input type='radio' name='radio-main'>"
"</label>"
"<label id='label3' role='radio'>"
" <input type='radio' name='radio-main'>"
"</label>",
ASSERT_NO_EXCEPTION);
const AXObject* ax_root = GetAXRootObject();
ASSERT_NE(nullptr, ax_root);
// (50, 50) initially hits #label1.
AXObject* hit_test_result = ax_root->AccessibilityHitTest({50, 50});
EXPECT_EQ(hit_test_result->RoleValue(), ax::mojom::Role::kRadioButton);
};
run_test(ShadowRootType::kOpen);
run_test(ShadowRootType::kClosed);
}
} // namespace blink
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