0

Allow AccessKeyAction to dispatch click to interactive pseudos

The ::scroll-button and ::scroll-marker pseudo-elements have
activation behavior that should be invoked on those pseudos
when activated by assistive tech

Bug: 397989399
Change-Id: I7fb6861e66f933eb41b3fc366c57291455b57c78
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6293546
Reviewed-by: Vladimir Levin <vmpstr@chromium.org>
Commit-Queue: Robert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1424098}
This commit is contained in:
Robert Flack
2025-02-24 12:43:04 -08:00
committed by Chromium LUCI CQ
parent 7bda9309f5
commit 01eddeb958
2 changed files with 69 additions and 3 deletions
third_party/blink/renderer
core
modules
accessibility

@ -577,9 +577,16 @@ Node* PseudoElement::InnerNodeForHitTesting() {
void PseudoElement::AccessKeyAction(
SimulatedClickCreationScope creation_scope) {
// Even though pseudo elements can't use the accesskey attribute, assistive
// tech can still attempt to interact with pseudo elements if they are in
// the AX tree (usually due to their text/image content).
// If this is a pseudo element with activation behavior such as a
// ::scroll-marker or ::scroll-button, we should invoke it.
if (HasActivationBehavior()) {
DispatchSimulatedClick(nullptr, creation_scope);
return;
}
// Even though regular pseudo elements can't use the accesskey attribute,
// assistive tech can still attempt to interact with pseudo elements if
// they are in the AX tree (usually due to their text/image content).
// Just pass this request to the originating element.
DCHECK(UltimateOriginatingElement());
UltimateOriginatingElement()->AccessKeyAction(creation_scope);

@ -1853,6 +1853,65 @@ TEST_F(AccessibilityTest, ScrollerFocusability) {
ASSERT_TRUE(scroller_node->IsFocused());
}
TEST_F(AccessibilityTest, ScrollButtonPseudoElement) {
SetBodyInnerHTML(R"HTML(
<style>
#scroller::scroll-button(block-end) {
content: "Scroll down";
}
</style>
<div id=scroller style="overflow:scroll;height:50px;">
<div id=content style="height:1000px"></div>
</div>
)HTML");
auto* scroller = GetElementById("scroller");
auto* scrollButton = GetAXObjectByElementId(
"scroller", PseudoId::kPseudoIdScrollButtonBlockEnd);
ui::AXActionData action_data;
action_data.action = ax::mojom::blink::Action::kDoDefault;
const ui::AXTreeID div_child_tree_id = ui::AXTreeID::CreateNewAXTreeID();
action_data.target_node_id = scrollButton->AXObjectID();
action_data.child_tree_id = div_child_tree_id;
scrollButton->PerformAction(action_data);
ASSERT_GT(scroller->scrollTop(), 0);
}
TEST_F(AccessibilityTest, ScrollMarkerPseudoElement) {
SetBodyInnerHTML(R"HTML(
<style>
#scroller {
scroll-marker-group: before;
}
#scroller::scroll-marker-group {
height: 100px;
width: 50px;
}
.marker::scroll-marker {
content: "Target";
height: 50px;
width: 50px;
}
</style>
<div id=scroller style="overflow:scroll;height:50px;">
<div class=marker></div>
<div id=content style="height:1000px"></div>
<div id=target class=marker></div>
</div>
)HTML");
auto* scroller = GetElementById("scroller");
auto* scrollMarker =
GetAXObjectByElementId("target", PseudoId::kPseudoIdScrollMarker);
ui::AXActionData action_data;
action_data.action = ax::mojom::blink::Action::kDoDefault;
const ui::AXTreeID div_child_tree_id = ui::AXTreeID::CreateNewAXTreeID();
action_data.target_node_id = scrollMarker->AXObjectID();
action_data.child_tree_id = div_child_tree_id;
scrollMarker->PerformAction(action_data);
ASSERT_GT(scroller->scrollTop(), 0);
}
TEST_F(AccessibilityTest, CanComputeAsNaturalParent) {
SetBodyInnerHTML(R"HTML(M<img usemap="#map"><map name="map"><hr><progress>
<div><input type="range">M)HTML");