[A11y] Add actions for elements with button or link children
Option and menuitem elements can have button or link children. Surface these children as implicit actions so that they are more discoverable. https://chromestatus.com/feature/5161589307867136 https://github.com/w3c/aria/issues/1440 https://github.com/w3c/aria/pull/1805 Bug: 369781734 Change-Id: Ia9b9d78b13dc2f2bd3a48d09b6d6db28c7026f01 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6147720 Commit-Queue: Jocelyn Tran <jocelyntran@google.com> Reviewed-by: Florin Malita <fmalita@chromium.org> Reviewed-by: Aaron Leventhal <aleventhal@chromium.org> Cr-Commit-Position: refs/heads/main@{#1405567}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
d70a55f2b2
commit
7dbd6a91b9
content
browser
accessibility
test
third_party/blink/renderer
@ -775,6 +775,13 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaActions) {
|
||||
RunAriaTest(FILE_PATH_LITERAL("aria-actions.html"));
|
||||
}
|
||||
|
||||
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
|
||||
AccessibilityAriaActionsImplicit) {
|
||||
base::CommandLine::ForCurrentProcess()->AppendSwitch(
|
||||
switches::kEnableExperimentalWebPlatformFeatures);
|
||||
RunAriaTest(FILE_PATH_LITERAL("aria-actions-implicit.html"));
|
||||
}
|
||||
|
||||
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
|
||||
AccessibilityAriaActionsReferenceTarget) {
|
||||
RunAriaTest(FILE_PATH_LITERAL("aria-actions-reference-target.html"));
|
||||
|
@ -388,6 +388,8 @@ data/accessibility/aria/aria-actions-expected-auralinux.txt
|
||||
data/accessibility/aria/aria-actions-expected-blink.txt
|
||||
data/accessibility/aria/aria-actions-expected-uia-win.txt
|
||||
data/accessibility/aria/aria-actions-expected-win.txt
|
||||
data/accessibility/aria/aria-actions-implicit-expected-blink.txt
|
||||
data/accessibility/aria/aria-actions-implicit.html
|
||||
data/accessibility/aria/aria-actions-reference-target-expected-blink.txt
|
||||
data/accessibility/aria/aria-actions-reference-target.html
|
||||
data/accessibility/aria/aria-actions-target-id-change-expected-blink.txt
|
||||
|
@ -0,0 +1,47 @@
|
||||
rootWebArea
|
||||
++genericContainer ignored
|
||||
++++genericContainer ignored
|
||||
++++++comboBoxSelect collapsed value='hello world'
|
||||
++++++++menuListPopup invisible ispopup=auto
|
||||
++++++++++menuListOption name='hello world' selected=true actionsIds=button
|
||||
++++++++++++genericContainer ignored
|
||||
++++++++++++++button name='hello world'
|
||||
++++++++++++++++staticText name='hello world'
|
||||
++++++++++menuListOption invisible name='search engine' selected=false actionsIds=link
|
||||
++++++++++++genericContainer ignored invisible
|
||||
++++++++++++++link invisible name='search engine'
|
||||
++++++++++++++++staticText invisible name='search engine'
|
||||
++++++++++menuListOption invisible name='my-first-button my-second-button my-link' selected=false actionsIds=button,button,link
|
||||
++++++++++++genericContainer ignored invisible
|
||||
++++++++++++++button invisible name='my-first-button'
|
||||
++++++++++++++++staticText invisible name='my-first-button'
|
||||
++++++++++++++button invisible name='my-second-button'
|
||||
++++++++++++++++staticText invisible name='my-second-button'
|
||||
++++++++++++++link invisible name='my-link'
|
||||
++++++++++++++++staticText invisible name='my-link'
|
||||
++++++listBox
|
||||
++++++++listBoxOption name='Item 1 hello world' selected=false actionsIds=button
|
||||
++++++++++staticText name='Item 1'
|
||||
++++++++++++inlineTextBox name='Item 1'
|
||||
++++++++++button name='hello world'
|
||||
++++++++++++staticText name='hello world'
|
||||
++++++++++++++inlineTextBox name='hello world'
|
||||
++++++++listBoxOption name='Item 2search engine' selected=false actionsIds=link
|
||||
++++++++++staticText name='Item 2'
|
||||
++++++++++++inlineTextBox name='Item 2'
|
||||
++++++++++link name='search engine'
|
||||
++++++++++++staticText name='search engine'
|
||||
++++++++++++++inlineTextBox name='search engine'
|
||||
++++++menu
|
||||
++++++++menuItem name='File1 hello world' actionsIds=button
|
||||
++++++++++staticText name='File1'
|
||||
++++++++++++inlineTextBox name='File1'
|
||||
++++++++++button name='hello world'
|
||||
++++++++++++staticText name='hello world'
|
||||
++++++++++++++inlineTextBox name='hello world'
|
||||
++++++++menuItem name='File2search engine' actionsIds=link
|
||||
++++++++++staticText name='File2'
|
||||
++++++++++++inlineTextBox name='File2'
|
||||
++++++++++link name='search engine'
|
||||
++++++++++++staticText name='search engine'
|
||||
++++++++++++++inlineTextBox name='search engine'
|
@ -0,0 +1,39 @@
|
||||
<!--
|
||||
@BLINK-ALLOW:actions*
|
||||
-->
|
||||
<select>
|
||||
<button>
|
||||
<selectedoption></selectedoption>
|
||||
</button>
|
||||
<option>
|
||||
<button type="button">hello world</button>
|
||||
</option>
|
||||
<option>
|
||||
<a href="google.com">search engine</a>
|
||||
</option>
|
||||
<option>
|
||||
<button type="button">my-first-button</button>
|
||||
<button type="button">my-second-button</button>
|
||||
<a href="google.com">my-link</a>
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div role="listbox">
|
||||
<div tabIndex="0" role="option">Item 1<button type="button">hello world</button></div>
|
||||
<div tabIndex="1" role="option">Item 2<a href="google.com">search engine</a></div>
|
||||
</div>
|
||||
|
||||
<div role="menu">
|
||||
<div tabindex="0" role="menuitem">File1<button type="button">hello world</button></div>
|
||||
<div tabindex="1" role="menuitem">File2<a href="google.com">search engine</a></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
selectedoption .description {
|
||||
display: none;
|
||||
}
|
||||
select, ::picker(select) {
|
||||
appearance: base-select;
|
||||
}
|
||||
</style>
|
||||
|
@ -1632,6 +1632,39 @@ void AXObject::SerializeColorAttributes(ui::AXNodeData* node_data) const {
|
||||
node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kColor, color);
|
||||
}
|
||||
|
||||
void AXObject::SerializeImplicitActions(ui::AXNodeData* node_data) const {
|
||||
// Serialize implicit actions for the following roles only.
|
||||
if (RoleValue() != ax::mojom::blink::Role::kMenuItem &&
|
||||
RoleValue() != ax::mojom::blink::Role::kListBoxOption &&
|
||||
RoleValue() != ax::mojom::blink::Role::kMenuListOption) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes the children of these elements are nested in a generic container,
|
||||
// skip the container to reach the actions we wish to surface.
|
||||
const AXObjectVector& children = ChildrenIncludingIgnored();
|
||||
bool hasContainer =
|
||||
children.size() == 1 &&
|
||||
(children[0]->RoleValue() == ax::mojom::blink::Role::kGenericContainer ||
|
||||
children[0]->RoleValue() == ax::mojom::blink::Role::kNone);
|
||||
const AXObjectVector& potential_actions =
|
||||
hasContainer ? children[0]->ChildrenIncludingIgnored() : children;
|
||||
|
||||
auto actions_ids = node_data->GetIntListAttribute(
|
||||
ax::mojom::blink::IntListAttribute::kActionsIds);
|
||||
for (const auto& child : potential_actions) {
|
||||
if (child->RoleValue() == ax::mojom::blink::Role::kButton ||
|
||||
child->RoleValue() == ax::mojom::blink::Role::kLink) {
|
||||
actions_ids.push_back(child->AXObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
if (!actions_ids.empty()) {
|
||||
node_data->AddIntListAttribute(
|
||||
ax::mojom::blink::IntListAttribute::kActionsIds, actions_ids);
|
||||
}
|
||||
}
|
||||
|
||||
void AXObject::SerializeElementAttributes(ui::AXNodeData* node_data) const {
|
||||
Element* element = GetElement();
|
||||
if (!element)
|
||||
@ -2594,6 +2627,12 @@ void AXObject::SerializeUnignoredAttributes(ui::AXNodeData* node_data,
|
||||
node_data->AddState(ax::mojom::blink::State::kHasActions);
|
||||
}
|
||||
|
||||
// Author-defined actions should take precedence over implicit ones.
|
||||
if (RuntimeEnabledFeatures::AccessibilityImplicitActionsEnabled() &&
|
||||
!HasAriaAttribute(html_names::kAriaActionsAttr)) {
|
||||
SerializeImplicitActions(node_data);
|
||||
}
|
||||
|
||||
if (IsScrollableContainer())
|
||||
SerializeScrollAttributes(node_data);
|
||||
|
||||
|
@ -1544,6 +1544,7 @@ class MODULES_EXPORT AXObject : public GarbageCollected<AXObject> {
|
||||
void SerializeChildTreeID(ui::AXNodeData* node_data) const;
|
||||
void SerializeChooserPopupAttributes(ui::AXNodeData* node_data) const;
|
||||
void SerializeColorAttributes(ui::AXNodeData* node_data) const;
|
||||
void SerializeImplicitActions(ui::AXNodeData* node_data) const;
|
||||
void SerializeElementAttributes(ui::AXNodeData* node_data) const;
|
||||
void SerializeHTMLNonStandardAttributesForJAWS(
|
||||
ui::AXNodeData* node_data) const;
|
||||
|
@ -240,6 +240,12 @@
|
||||
name: "AccessibilityExposeDisplayNone",
|
||||
status: "test",
|
||||
},
|
||||
{
|
||||
// If the author did not define aria-actions, surface button and link
|
||||
// children inside option and menuitem elements as implicit actions.
|
||||
name: "AccessibilityImplicitActions",
|
||||
status: "experimental",
|
||||
},
|
||||
{
|
||||
// Use a minimum role of group on elements that are keyboard-focusable.
|
||||
// See https://w3c.github.io/html-aam/#minimum-role.
|
||||
|
Reference in New Issue
Block a user