LibWeb: Handle accessible-name computation for shadow roots and slots
This change adds handling for the “Determine Child Nodes” substep at https://w3c.github.io/accname/#comp_name_from_content_find_child in the “Accessible Name and Description Computation” spec. Specifically, it adds handling for the “If the current node has an attached shadow root” and “if the current node is a slot with assigned nodes” conditions. Otherwise, without this change, AT users don’t hear the expected accessible names in cases where the content for which an accessible name being computed is in a shadow root or slot element.
This commit is contained in:
parent
6bb8bf189f
commit
e2a7f844e6
Notes:
github-actions[bot]
2024-11-25 10:53:42 +00:00
Author: https://github.com/sideshowbarker Commit: https://github.com/LadybirdBrowser/ladybird/commit/e2a7f844e69 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2562
5 changed files with 149 additions and 18 deletions
|
@ -2365,8 +2365,9 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
|
|||
// is not the empty string:
|
||||
if (target == NameOrDescription::Name && element->aria_label().has_value() && !element->aria_label()->is_empty() && !element->aria_label()->bytes_as_string_view().is_whitespace()) {
|
||||
// TODO: - If traversal of the current node is due to recursion and the current node is an embedded control as defined in step 2E, ignore aria-label and skip to rule 2E.
|
||||
// - Otherwise, return the value of aria-label.
|
||||
return element->aria_label().value();
|
||||
// https://github.com/w3c/aria/pull/2385 and https://github.com/w3c/accname/issues/173
|
||||
if (!element->is_html_slot_element())
|
||||
return element->aria_label().value();
|
||||
}
|
||||
|
||||
// E. Host Language Label: Otherwise, if the current node's native markup provides an attribute (e.g. alt) or
|
||||
|
@ -2444,39 +2445,46 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
|
|||
else
|
||||
total_accumulated_text.append(before->computed_values().content().data);
|
||||
}
|
||||
// iii. For each child node of the current node:
|
||||
element->for_each_child([&total_accumulated_text, current_node, target, &document, &visited_nodes](
|
||||
DOM::Node const& child_node) mutable {
|
||||
if (!child_node.is_element() && !child_node.is_text())
|
||||
return IterationDecision::Continue;
|
||||
// iii. Determine Child Nodes: Determine the rendered child nodes of the current node:
|
||||
// iii. Determine Child Nodes: Determine the rendered child nodes of the current node:
|
||||
// c. [Otherwise,] set the rendered child nodes to be the child nodes of the current node.
|
||||
auto child_nodes = current_node->children_as_vector();
|
||||
// a. If the current node has an attached shadow root, set the rendered child nodes to be the child nodes of
|
||||
// the shadow root.
|
||||
if (element->is_shadow_host() && element->shadow_root() && element->shadow_root()->is_connected())
|
||||
child_nodes = element->shadow_root()->children_as_vector();
|
||||
// b. Otherwise, if the current node is a slot with assigned nodes, set the rendered child nodes to be the
|
||||
// assigned nodes of the current node.
|
||||
if (element->is_html_slot_element()) {
|
||||
total_accumulated_text.append(element->text_content().value());
|
||||
child_nodes = static_cast<HTML::HTMLSlotElement const*>(element)->assigned_nodes();
|
||||
}
|
||||
// iv. Name From Each Child: For each rendered child node of the current node
|
||||
for (auto& child_node : child_nodes) {
|
||||
if (!child_node->is_element() && !child_node->is_text())
|
||||
continue;
|
||||
bool should_add_space = true;
|
||||
const_cast<DOM::Document&>(document).update_layout();
|
||||
auto const* layout_node = child_node.layout_node();
|
||||
auto const* layout_node = child_node->layout_node();
|
||||
if (layout_node) {
|
||||
auto display = layout_node->display();
|
||||
if (display.is_inline_outside() && display.is_flow_inside()) {
|
||||
should_add_space = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (visited_nodes.contains(child_node.unique_id()))
|
||||
return IterationDecision::Continue;
|
||||
|
||||
if (visited_nodes.contains(child_node->unique_id()))
|
||||
continue;
|
||||
// a. Set the current node to the child node.
|
||||
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.
|
||||
auto result = MUST(current_node->name_or_description(target, document, visited_nodes, IsDescendant::Yes));
|
||||
|
||||
// Append a space character and the result of each step above to the total accumulated text.
|
||||
// AD-HOC: Doing the space-adding here is in a different order from what the spec states.
|
||||
if (should_add_space)
|
||||
total_accumulated_text.append(' ');
|
||||
// c. Append the result to the accumulated text.
|
||||
total_accumulated_text.append(result);
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
// NOTE: See step ii.b above.
|
||||
if (auto after = element->get_pseudo_element_node(CSS::Selector::PseudoElement::Type::After)) {
|
||||
if (after->computed_values().content().alt_text.has_value())
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
Summary
|
||||
|
||||
Harness status: OK
|
||||
|
||||
Rerun
|
||||
|
||||
Found 2 tests
|
||||
|
||||
2 Pass
|
||||
Details
|
||||
Result Test Name MessagePass aria-labelledby reference to element with text content inside shadow DOM
|
||||
Pass aria-labelledby reference to element with aria-label inside shadow DOM
|
|
@ -0,0 +1,14 @@
|
|||
Summary
|
||||
|
||||
Harness status: OK
|
||||
|
||||
Rerun
|
||||
|
||||
Found 4 tests
|
||||
|
||||
4 Pass
|
||||
Details
|
||||
Result Test Name MessagePass aria-labelledby reference to element with slotted text content
|
||||
Pass aria-labelledby reference to element with default slotted text content
|
||||
Pass aria-labelledby reference to element with slotted text content and aria-label on slot
|
||||
Pass aria-labelledby reference to element with default slotted text content and aria-label on slot
|
|
@ -0,0 +1,37 @@
|
|||
<!doctype html>
|
||||
<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>
|
||||
|
||||
<p>Tests the basic shadow DOM portions of the AccName <em>Name Computation</em> algorithm, coming in <a href="https://github.com/w3c/accname/pull/167">ARIA #167</a>.</p>
|
||||
|
||||
<label id="label1">
|
||||
<div id="host1"></div>
|
||||
</label>
|
||||
<button id="labelled1"
|
||||
class="labelled"
|
||||
type="button"
|
||||
aria-labelledby="label1"
|
||||
data-expectedlabel="foo"
|
||||
data-testname="aria-labelledby reference to element with text content inside shadow DOM"></button>
|
||||
|
||||
<label id="label2">
|
||||
<div id="host2"></div>
|
||||
</label>
|
||||
<button id="labelled2"
|
||||
class="labelled"
|
||||
type="button"
|
||||
aria-labelledby="label2"
|
||||
data-expectedlabel="bar"
|
||||
data-testname="aria-labelledby reference to element with aria-label inside shadow DOM"></button>
|
||||
<script>
|
||||
|
||||
document.getElementById('host1').attachShadow({ mode: 'open' }).innerHTML = 'foo';
|
||||
document.getElementById('host2').attachShadow({ mode: 'open' }).innerHTML = '<div aria-label="bar"></div>';
|
||||
|
||||
AriaUtils.verifyLabelsBySelector('.labelled');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,60 @@
|
|||
<!doctype html>
|
||||
<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>
|
||||
|
||||
<p>Tests the shadow DOM slots portions of the AccName <em>Name Computation</em> algorithm, coming in <a href="https://github.com/w3c/accname/pull/167">ARIA #167</a>.</p>
|
||||
|
||||
<label id="label1">
|
||||
<div id="host1">slotted</div>
|
||||
</label>
|
||||
<button id="labelled1"
|
||||
class="labelled"
|
||||
type="button"
|
||||
aria-labelledby="label1"
|
||||
data-expectedlabel="foo slotted bar"
|
||||
data-testname="aria-labelledby reference to element with slotted text content"></button>
|
||||
|
||||
<label id="label2">
|
||||
<div id="host2"></div>
|
||||
</label>
|
||||
<button id="labelled2"
|
||||
class="labelled"
|
||||
type="button"
|
||||
aria-labelledby="label2"
|
||||
data-expectedlabel="foo default bar"
|
||||
data-testname="aria-labelledby reference to element with default slotted text content"></button>
|
||||
|
||||
<label id="label3">
|
||||
<div id="host3">slotted</div>
|
||||
</label>
|
||||
<button id="labelled3"
|
||||
class="labelled"
|
||||
type="button"
|
||||
aria-labelledby="label3"
|
||||
data-expectedlabel="foo slotted bar"
|
||||
data-testname="aria-labelledby reference to element with slotted text content and aria-label on slot"></button>
|
||||
|
||||
<label id="label4">
|
||||
<div id="host4"></div>
|
||||
</label>
|
||||
<button id="labelled4"
|
||||
class="labelled"
|
||||
type="button"
|
||||
aria-labelledby="label4"
|
||||
data-expectedlabel="foo default bar"
|
||||
data-testname="aria-labelledby reference to element with default slotted text content and aria-label on slot"></button>
|
||||
|
||||
<script>
|
||||
|
||||
document.getElementById('host1').attachShadow({ mode: 'open' }).innerHTML = 'foo <slot></slot> bar';
|
||||
document.getElementById('host2').attachShadow({ mode: 'open' }).innerHTML = 'foo <slot>default</slot> bar';
|
||||
document.getElementById('host3').attachShadow({ mode: 'open' }).innerHTML = 'foo <slot aria-label="label"></slot> bar';
|
||||
document.getElementById('host4').attachShadow({ mode: 'open' }).innerHTML = 'foo <slot aria-label="label">default</slot> bar';
|
||||
|
||||
AriaUtils.verifyLabelsBySelector('.labelled');
|
||||
|
||||
</script>
|
Loading…
Add table
Reference in a new issue