mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 01:20:25 +00:00
LibWeb: Correctly descend element nodes when computing accessible name
This change implements the “is a descendant of a native host language text alternative element” condition in the “F: Name From Content” step at https://w3c.github.io/accname/#step2F in the “Accessible Name and Description Computation” spec — to ensure that all descendant nodes get included as expected in computations for accessible names for elements. Otherwise, without this change, Ladybird unexpectedly skips descendant element nodes when computing accessible names — which can result in the wrong accessible name being returned.
This commit is contained in:
parent
aeab342fd7
commit
437879f849
Notes:
github-actions[bot]
2024-11-01 22:14:36 +00:00
Author: https://github.com/sideshowbarker Commit: https://github.com/LadybirdBrowser/ladybird/commit/437879f8496 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2099 Reviewed-by: https://github.com/gmta Reviewed-by: https://github.com/trflynn89 ✅
4 changed files with 210 additions and 4 deletions
|
@ -0,0 +1,60 @@
|
||||||
|
Summary
|
||||||
|
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Rerun
|
||||||
|
|
||||||
|
Found 50 tests
|
||||||
|
|
||||||
|
50 Pass
|
||||||
|
Details
|
||||||
|
Result Test Name MessagePass span[role=button] with text/element/text nodes, no space
|
||||||
|
Pass div[role=heading] with text/element/text nodes, no space
|
||||||
|
Pass button with text/element/text nodes, no space
|
||||||
|
Pass heading with text/element/text nodes, no space
|
||||||
|
Pass link with text/element/text nodes, no space
|
||||||
|
Pass span[role=button] with text/comment/text nodes, no space
|
||||||
|
Pass div[role=heading] with text/comment/text nodes, no space
|
||||||
|
Pass button with text/comment/text nodes, no space
|
||||||
|
Pass heading with text/comment/text nodes, no space
|
||||||
|
Pass link with text/comment/text nodes, no space
|
||||||
|
Pass span[role=button] with text/comment/text nodes, with space
|
||||||
|
Pass div[role=heading] with text/comment/text nodes, with space
|
||||||
|
Pass button with text/comment/text nodes, with space
|
||||||
|
Pass heading with text/comment/text nodes, with space
|
||||||
|
Pass link with text/comment/text nodes, with space
|
||||||
|
Pass span[role=button] with text node, with tab char
|
||||||
|
Pass div[role=heading] with text node, with tab char
|
||||||
|
Pass button with text node, with tab char
|
||||||
|
Pass heading with text node, with tab char
|
||||||
|
Pass link with text node, with tab char
|
||||||
|
Pass span[role=button] with text node, with non-breaking space
|
||||||
|
Pass div[role=heading] with text node, with non-breaking space
|
||||||
|
Pass button with text node, with non-breaking space
|
||||||
|
Pass heading with text node, with non-breaking space
|
||||||
|
Pass link with text node, with non-breaking space
|
||||||
|
Pass span[role=button] with text node, with extra non-breaking space
|
||||||
|
Pass div[role=heading] with text node, with extra non-breaking space
|
||||||
|
Pass button with text node, with extra non-breaking space
|
||||||
|
Pass heading with text node, with extra non-breaking space
|
||||||
|
Pass link with text node, with extra non-breaking space
|
||||||
|
Pass span[role=button] with text node, with leading/trailing non-breaking space
|
||||||
|
Pass div[role=heading] with text node, with leading/trailing non-breaking space
|
||||||
|
Pass button with text node, with leading/trailing non-breaking space
|
||||||
|
Pass heading with text node, with leading/trailing non-breaking space
|
||||||
|
Pass link with text node, with leading/trailing non-breaking space
|
||||||
|
Pass span[role=button] with text node, with mixed space and non-breaking space
|
||||||
|
Pass div[role=heading] with text node, with mixed space and non-breaking space
|
||||||
|
Pass button with text node, with mixed space and non-breaking space
|
||||||
|
Pass heading with text node, with mixed space and non-breaking space
|
||||||
|
Pass link with text node, with mixed space and non-breaking space
|
||||||
|
Pass span[role=button] with text node, with deeply nested space
|
||||||
|
Pass div[role=heading] with text node, with deeply nested space
|
||||||
|
Pass button with text node, with deeply nested space
|
||||||
|
Pass heading with text node, with deeply nested space
|
||||||
|
Pass link with text node, with deeply nested space
|
||||||
|
Pass span[role=button] with text node, with single line break
|
||||||
|
Pass div[role=heading] with text node, with single line break
|
||||||
|
Pass button with text node, with single line break
|
||||||
|
Pass heading with text node, with single line break
|
||||||
|
Pass link with text node, with single line break
|
|
@ -0,0 +1,141 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Name Comp: Text Node</title>
|
||||||
|
<script src="../../resources/testharness.js"></script>
|
||||||
|
<script src="../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../resources/testdriver.js"></script>
|
||||||
|
<script src="../../resources/testdriver-vendor.js"></script>
|
||||||
|
<script src="../../resources/testdriver-actions.js"></script>
|
||||||
|
<script src="../../wai-aria/scripts/aria-utils.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Tests the <a href="https://w3c.github.io/accname/#comp_text_node">#comp_text_node</a> portions of the AccName <em>Name Computation</em> algorithm.</p>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Note: some overlap with the tests in:
|
||||||
|
- /accname/name/comp_label.html
|
||||||
|
- /accname/name/comp_name_from_content.html
|
||||||
|
|
||||||
|
-->
|
||||||
|
<h1>text/element/text nodes, no space</h1>
|
||||||
|
<span class="ex" data-expectedlabel="buttonlabel" data-testname="span[role=button] with text/element/text nodes, no space" role="button" tabindex="0">button<span></span>label</span>
|
||||||
|
<div class="ex" data-expectedlabel="headinglabel" data-testname="div[role=heading] with text/element/text nodes, no space" role="heading">heading<span></span>label</div>
|
||||||
|
<button class="ex" data-expectedlabel="buttonlabel" data-testname="button with text/element/text nodes, no space">button<span></span>label</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="headinglabel" data-testname="heading with text/element/text nodes, no space">heading<span></span>label</h3>
|
||||||
|
<a class="ex" data-expectedlabel="linklabel" data-testname="link with text/element/text nodes, no space" href="#">link<span></span>label</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text/comment/text nodes, no space</h1>
|
||||||
|
<!-- Note: This set is not currently to spec until https://github.com/w3c/accname/issues/193 is resolved. -->
|
||||||
|
<span class="ex" data-expectedlabel="buttonlabel" data-testname="span[role=button] with text/comment/text nodes, no space" role="button" tabindex="0">
|
||||||
|
button<!-- with non-text node splitting concatenated text nodes -->label<!-- [sic] no extra spaces around first comment -->
|
||||||
|
</span>
|
||||||
|
<div class="ex" data-expectedlabel="headinglabel" data-testname="div[role=heading] with text/comment/text nodes, no space" role="heading">
|
||||||
|
heading<!-- with non-text node splitting concatenated text nodes -->label<!-- [sic] no extra spaces around first comment -->
|
||||||
|
</div>
|
||||||
|
<button class="ex" data-expectedlabel="buttonlabel" data-testname="button with text/comment/text nodes, no space">
|
||||||
|
button<!-- with non-text node splitting concatenated text nodes -->label<!-- [sic] no extra spaces around first comment -->
|
||||||
|
</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="headinglabel" data-testname="heading with text/comment/text nodes, no space">
|
||||||
|
heading<!-- with non-text node splitting concatenated text nodes -->label<!-- [sic] no extra spaces around first comment -->
|
||||||
|
</h3>
|
||||||
|
<a class="ex" data-expectedlabel="linklabel" data-testname="link with text/comment/text nodes, no space" href="#">
|
||||||
|
link<!-- with non-text node splitting concatenated text nodes -->label<!-- [sic] no extra spaces around first comment -->
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text/comment/text nodes, with space</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text/comment/text nodes, with space" role="button" tabindex="0">
|
||||||
|
button
|
||||||
|
<!-- comment node between text nodes with leading/trailing whitespace -->
|
||||||
|
label
|
||||||
|
</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text/comment/text nodes, with space" role="heading">
|
||||||
|
heading
|
||||||
|
<!-- comment node between text nodes with leading/trailing whitespace -->
|
||||||
|
label
|
||||||
|
</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text/comment/text nodes, with space">
|
||||||
|
button
|
||||||
|
<!-- comment node between text nodes with leading/trailing whitespace -->
|
||||||
|
label
|
||||||
|
</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text/comment/text nodes, with space">
|
||||||
|
heading
|
||||||
|
<!-- comment node between text nodes with leading/trailing whitespace -->
|
||||||
|
label
|
||||||
|
</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text/comment/text nodes, with space" href="#">
|
||||||
|
link
|
||||||
|
<!-- comment node between text nodes with leading/trailing whitespace -->
|
||||||
|
label
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text node, with tab char</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text node, with tab char" role="button" tabindex="0">button label</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text node, with tab char" role="heading">heading label</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text node, with tab char">button label</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text node, with tab char">heading label</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text node, with tab char" href="#">link label</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text node, with non-breaking space</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text node, with non-breaking space" role="button" tabindex="0">button label</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text node, with non-breaking space" role="heading">heading label</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text node, with non-breaking space">button label</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text node, with non-breaking space">heading label</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text node, with non-breaking space" href="#">link label</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text node, with extra non-breaking space</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text node, with extra non-breaking space" role="button" tabindex="0">button label</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text node, with extra non-breaking space" role="heading">heading label</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text node, with extra non-breaking space">button label</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text node, with extra non-breaking space">heading label</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text node, with extra non-breaking space" href="#">link label</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text node, with leading/trailing non-breaking space</h1>
|
||||||
|
<span class="ex" data-expectedlabel=" button label " data-testname="span[role=button] with text node, with leading/trailing non-breaking space" role="button" tabindex="0"> button label </span>
|
||||||
|
<div class="ex" data-expectedlabel=" heading label " data-testname="div[role=heading] with text node, with leading/trailing non-breaking space" role="heading"> heading label </div>
|
||||||
|
<button class="ex" data-expectedlabel=" button label " data-testname="button with text node, with leading/trailing non-breaking space"> button label </button>
|
||||||
|
<h3 class="ex" data-expectedlabel=" heading label " data-testname="heading with text node, with leading/trailing non-breaking space"> heading label </h3>
|
||||||
|
<a class="ex" data-expectedlabel=" link label " data-testname="link with text node, with leading/trailing non-breaking space" href="#"> link label </a>
|
||||||
|
<br/>
|
||||||
|
<h1>text node, with mixed space and non-breaking space</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text node, with mixed space and non-breaking space" role="button" tabindex="0">button label</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text node, with mixed space and non-breaking space" role="heading">heading label</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text node, with mixed space and non-breaking space">button label</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text node, with mixed space and non-breaking space">heading label</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text node, with mixed space and non-breaking space" href="#">link label</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text nodes, with deeply nested space</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text node, with deeply nested space" role="button" tabindex="0">
|
||||||
|
button<span><span><span><span><span><span><span> </span></span></span></span></span></span></span>label
|
||||||
|
</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text node, with deeply nested space" role="heading">
|
||||||
|
heading<span><span><span><span><span><span><span> </span></span></span></span></span></span></span>label
|
||||||
|
</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text node, with deeply nested space">
|
||||||
|
button<span><span><span><span><span><span><span> </span></span></span></span></span></span></span>label
|
||||||
|
</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text node, with deeply nested space">
|
||||||
|
heading<span><span><span><span><span><span><span> </span></span></span></span></span></span></span>label
|
||||||
|
</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text node, with deeply nested space" href="#">
|
||||||
|
link<span><span><span><span><span><span><span> </span></span></span></span></span></span></span>label
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<h1>text nodes, with single line break</h1>
|
||||||
|
<span class="ex" data-expectedlabel="button label" data-testname="span[role=button] with text node, with single line break" role="button" tabindex="0">button
|
||||||
|
label</span>
|
||||||
|
<div class="ex" data-expectedlabel="heading label" data-testname="div[role=heading] with text node, with single line break" role="heading">heading
|
||||||
|
label</div>
|
||||||
|
<button class="ex" data-expectedlabel="button label" data-testname="button with text node, with single line break">button
|
||||||
|
label</button>
|
||||||
|
<h3 class="ex" data-expectedlabel="heading label" data-testname="heading with text node, with single line break">heading
|
||||||
|
label</h3>
|
||||||
|
<a class="ex" data-expectedlabel="link label" data-testname="link with text node, with single line break" href="#">link
|
||||||
|
label</a>
|
||||||
|
<br/>
|
||||||
|
<script>
|
||||||
|
AriaUtils.verifyLabelsBySelector(".ex");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -2181,7 +2181,7 @@ void Node::build_accessibility_tree(AccessibilityTreeNode& parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
|
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
|
||||||
ErrorOr<String> Node::name_or_description(NameOrDescription target, Document const& document, HashTable<UniqueNodeID>& visited_nodes) const
|
ErrorOr<String> Node::name_or_description(NameOrDescription target, Document const& document, HashTable<UniqueNodeID>& visited_nodes, IsDescendant is_descendant) const
|
||||||
{
|
{
|
||||||
// The text alternative for a given element is computed as follows:
|
// The text alternative for a given element is computed as follows:
|
||||||
// 1. Set the root node to the given element, the current node to the root node, and the total accumulated text to the empty string (""). If the root node's role prohibits naming, return the empty string ("").
|
// 1. Set the root node to the given element, the current node to the root node, and the total accumulated text to the empty string (""). If the root node's role prohibits naming, return the empty string ("").
|
||||||
|
@ -2321,7 +2321,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
|
||||||
|
|
||||||
// F. Otherwise, if the current node's role allows name from content, or if the current node is referenced by aria-labelledby, aria-describedby, or is a native host language text alternative element (e.g. label in HTML), or is a descendant of a native host language text alternative element:
|
// F. Otherwise, if the current node's role allows name from content, or if the current node is referenced by aria-labelledby, aria-describedby, or is a native host language text alternative element (e.g. label in HTML), or is a descendant of a native host language text alternative element:
|
||||||
auto role = element->role_or_default();
|
auto role = element->role_or_default();
|
||||||
if (role.has_value() && ARIA::allows_name_from_content(role.value())) {
|
if ((role.has_value() && ARIA::allows_name_from_content(role.value())) || is_descendant == IsDescendant::Yes) {
|
||||||
// i. Set the accumulated text to the empty string.
|
// i. Set the accumulated text to the empty string.
|
||||||
total_accumulated_text.clear();
|
total_accumulated_text.clear();
|
||||||
// ii. Check for CSS generated textual content associated with the current node and include it in the accumulated text. The CSS :before and :after pseudo elements [CSS2] can provide textual content for elements that have a content model.
|
// ii. Check for CSS generated textual content associated with the current node and include it in the accumulated text. The CSS :before and :after pseudo elements [CSS2] can provide textual content for elements that have a content model.
|
||||||
|
@ -2345,7 +2345,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
|
||||||
current_node = &child_node;
|
current_node = &child_node;
|
||||||
|
|
||||||
// b. Compute the text alternative of the current node beginning with step 2. Set the result to that text alternative.
|
// b. Compute the text alternative of the current node beginning with step 2. Set the result to that text alternative.
|
||||||
auto result = MUST(current_node->name_or_description(target, document, visited_nodes));
|
auto result = MUST(current_node->name_or_description(target, document, visited_nodes, IsDescendant::Yes));
|
||||||
|
|
||||||
// c. Append the result to the accumulated text.
|
// c. Append the result to the accumulated text.
|
||||||
total_accumulated_text.append(result);
|
total_accumulated_text.append(result);
|
||||||
|
|
|
@ -53,6 +53,11 @@ enum class FragmentSerializationMode {
|
||||||
Outer,
|
Outer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class IsDescendant {
|
||||||
|
No,
|
||||||
|
Yes,
|
||||||
|
};
|
||||||
|
|
||||||
#define ENUMERATE_STYLE_INVALIDATION_REASONS(X) \
|
#define ENUMERATE_STYLE_INVALIDATION_REASONS(X) \
|
||||||
X(AdoptedStyleSheetsList) \
|
X(AdoptedStyleSheetsList) \
|
||||||
X(CSSFontLoaded) \
|
X(CSSFontLoaded) \
|
||||||
|
@ -761,7 +766,7 @@ protected:
|
||||||
|
|
||||||
void build_accessibility_tree(AccessibilityTreeNode& parent);
|
void build_accessibility_tree(AccessibilityTreeNode& parent);
|
||||||
|
|
||||||
ErrorOr<String> name_or_description(NameOrDescription, Document const&, HashTable<UniqueNodeID>&) const;
|
ErrorOr<String> name_or_description(NameOrDescription, Document const&, HashTable<UniqueNodeID>&, IsDescendant = IsDescendant::No) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void queue_tree_mutation_record(Vector<JS::Handle<Node>> added_nodes, Vector<JS::Handle<Node>> removed_nodes, Node* previous_sibling, Node* next_sibling);
|
void queue_tree_mutation_record(Vector<JS::Handle<Node>> added_nodes, Vector<JS::Handle<Node>> removed_nodes, Node* previous_sibling, Node* next_sibling);
|
||||||
|
|
Loading…
Reference in a new issue