This commit is contained in:
sideshowbarker 2024-11-21 15:42:52 +09:00 committed by GitHub
commit 2450a28b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 501 additions and 34 deletions

View file

@ -1874,6 +1874,54 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute
invalidate_style(StyleInvalidationReason::ElementAttributeChange);
}
bool Element::is_hidden() const
{
if (layout_node() == nullptr)
return true;
if ((layout_node() != nullptr) && (layout_node()->computed_values().visibility() == CSS::Visibility::Hidden || layout_node()->computed_values().visibility() == CSS::Visibility::Collapse || layout_node()->computed_values().content_visibility() == CSS::ContentVisibility::Hidden))
return true;
for (ParentNode const* self_or_ancestor = this; self_or_ancestor; self_or_ancestor = self_or_ancestor->parent_or_shadow_host()) {
if (self_or_ancestor->is_element() && static_cast<DOM::Element const*>(self_or_ancestor)->aria_hidden() == "true")
return true;
}
return false;
}
bool Element::has_hidden_ancestor() const
{
for (ParentNode const* self_or_ancestor = this; self_or_ancestor; self_or_ancestor = self_or_ancestor->parent_or_shadow_host()) {
if (self_or_ancestor->is_element() && static_cast<DOM::Element const*>(self_or_ancestor)->is_hidden())
return true;
}
return false;
}
bool Element::is_referenced() const
{
bool is_referenced = false;
if (id().has_value()) {
this->root().for_each_in_inclusive_subtree_of_type<HTML::HTMLElement>([&](auto& element) {
auto aria_data = MUST(Web::ARIA::AriaData::build_data(element));
if (aria_data->aria_labelled_by_or_default().contains_slow(id().value())) {
is_referenced = true;
return TraversalDecision::Break;
}
return TraversalDecision::Continue;
});
}
return is_referenced;
}
bool Element::has_referenced_and_hidden_ancestor() const
{
for (auto const* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host()) {
if (ancestor->is_element())
if (auto const* element = static_cast<DOM::Element const*>(ancestor); element->is_referenced() && element->is_hidden())
return true;
}
return false;
}
// https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
bool Element::exclude_from_accessibility_tree() const
{

View file

@ -348,6 +348,12 @@ public:
virtual bool include_in_accessibility_tree() const override;
bool is_hidden() const;
bool has_hidden_ancestor() const;
bool is_referenced() const;
bool has_referenced_and_hidden_ancestor() const;
void enqueue_a_custom_element_upgrade_reaction(HTML::CustomElementDefinition& custom_element_definition);
void enqueue_a_custom_element_callback_reaction(FlyString const& callback_name, GC::MarkedVector<JS::Value> arguments);

View file

@ -2206,23 +2206,25 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
if (is_element()) {
auto const* element = static_cast<DOM::Element const*>(this);
auto role = element->role_or_default();
bool is_referenced = false;
auto id = element->id();
if (id.has_value()) {
this->root().for_each_in_inclusive_subtree_of_type<HTML::HTMLElement>([&](auto& element) {
auto aria_data = MUST(Web::ARIA::AriaData::build_data(element));
if (aria_data->aria_labelled_by_or_default().contains_slow(id.value())) {
is_referenced = true;
return TraversalDecision::Break;
}
return TraversalDecision::Continue;
});
}
// 2. Compute the text alternative for the current node:
// A. If the current node is hidden and is not directly referenced by aria-labelledby or aria-describedby, nor directly referenced by a native host language text alternative element (e.g. label in HTML) or attribute, return the empty string.
// FIXME: Check for references
if (element->aria_hidden() == "true")
return String {};
// A. Hidden Not Referenced: If the current node is hidden and is:
// i. Not part of an aria-labelledby or aria-describedby traversal, where the node directly referenced by that
// relation was hidden.
// ii. Nor part of a native host language text alternative element (e.g. label in HTML) or attribute traversal,
// where the root of that traversal was hidden.
// Return the empty string.
// NOTE: Nodes with CSS properties display:none, visibility:hidden, visibility:collapse or
// content-visibility:hidden: They are considered hidden, as they match the guidelines "not perceivable" and
// "explicitly hidden".
//
// AD-HOC: We dont implement this step here — because strictly implementing this would cause us to return early
// whenever encountering a node (element, actually) that “is hidden and is not directly referenced by
// aria-labelledby or aria-describedby”, without traversing down through that elements subtree to see if it has
// (1) any descendant elements that are directly referenced and/or (2) any un-hidden nodes. So we instead (in
// substep G below) traverse upward through ancestor nodes of every text node, and check in that way to do the
// equivalent of what this step seems to have been intended to do.
// B. Otherwise:
// - if computing a name, and the current node has an aria-labelledby attribute that contains at least one valid IDREF, and the current node is not already part of an aria-labelledby traversal,
// process its IDREFs in the order they occur:
@ -2265,13 +2267,31 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
total_accumulated_text.append(result);
}
// iii. Return the accumulated text.
// AD-HOC: This substep in the spec doesnt seem to explicitly require the following check for an aria-label
// value; but the “button's hidden referenced name (visibility:hidden) with hidden aria-labelledby traversal
// falls back to aria-label” subtest at https://wpt.fyi/results/accname/name/comp_labelledby.html wont pass
// unless we do this check.
if (total_accumulated_text.to_string().value().bytes_as_string_view().is_whitespace() && target == NameOrDescription::Name && element->aria_label().has_value() && !element->aria_label()->is_empty() && !element->aria_label()->bytes_as_string_view().is_whitespace())
return element->aria_label().value();
return total_accumulated_text.to_string();
}
// C. Embedded Control: Otherwise, if the current node is a control embedded
// within the label (e.g. any element directly referenced by aria-labelledby) for
// another widget, where the user can adjust the embedded control's value, then
// return the embedded control as part of the text alternative in the following
// manner:
// D. AriaLabel: Otherwise, if the current node has an aria-label attribute whose value is not undefined, not
// the empty string, nor, when trimmed of whitespace, is not the empty string:
// AD-HOC: Weve reordered substeps C and D from https://w3c.github.io/accname/#step2 — because
// the more-specific per-HTML-element requirements at https://w3c.github.io/html-aam/#accname-computation
// necessitate doing so, and the “input with label for association is superceded by aria-label” subtest at
// https://wpt.fyi/results/accname/name/comp_label.html wont pass unless we do this reordering.
// Spec PR: https://github.com/w3c/aria/pull/2377
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();
}
// C. Embedded Control: Otherwise, if the current node is a control embedded within the label (e.g. any element
// directly referenced by aria-labelledby) for another widget, where the user can adjust the embedded control's
// value, then return the embedded control as part of the text alternative in the following manner:
GC::Ptr<DOM::NodeList> labels;
if (is<HTML::HTMLElement>(this))
labels = (const_cast<HTML::HTMLElement&>(static_cast<HTML::HTMLElement const&>(*current_node))).labels();
@ -2341,15 +2361,6 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
return builder.to_string();
}
// D. AriaLabel: Otherwise, if the current node has an aria-label attribute whose
// value is not undefined, not the empty string, nor, when trimmed of whitespace,
// 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();
}
// E. Host Language Label: Otherwise, if the current node's native markup provides an attribute (e.g. alt) or
// element (e.g. HTML label or SVG title) that defines a text alternative, return that alternative in the form
// of a flat string as defined by the host language, unless the element is marked as presentational
@ -2361,8 +2372,10 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// with the spec requirements — and if not, then add handling for it here.
}
// 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:
if ((role.has_value() && ARIA::allows_name_from_content(role.value())) || is_referenced || is_descendant == IsDescendant::Yes) {
// F. Name From Content: 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:
if ((role.has_value() && ARIA::allows_name_from_content(role.value())) || element->is_referenced() || is_descendant == IsDescendant::Yes) {
// i. Set the accumulated text to the empty string.
total_accumulated_text.clear();
// ii. Name From Generated Content: Check for CSS generated textual content associated with the current node and include
@ -2427,13 +2440,24 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
}
// G. Text Node: Otherwise, if the current node is a Text Node, return its textual contents.
if (is_text()) {
// AD-HOC: The spec doesnt require ascending through the parent node and ancestor nodes of every text node we
// reach — the way were doing there. But we implement it this way because the spec algorithm as written doesnt
// appear to achieve what it seems to be intended to achieve. Specifically, the spec algorithm as written doesnt
// cause traversal through element subtrees in way thats necessary to check for descendants that are referenced by
// aria-labelledby or aria-describedby and/or un-hidden. See the comment for substep A above.
if (is_text() && (parent_element()->is_referenced() || !parent_element()->is_hidden() || !parent_element()->has_hidden_ancestor() || parent_element()->has_referenced_and_hidden_ancestor())) {
if (layout_node() && layout_node()->is_text_node())
return verify_cast<Layout::TextNode>(layout_node())->text_for_rendering();
return text_content().value();
}
// TODO: H. Otherwise, if the current node is a descendant of an element whose Accessible Name or Accessible Description is being computed, and contains descendants, proceed to 2F.i.
// H. Otherwise, if the current node is a descendant of an element whose Accessible Name or Accessible Description
// is being computed, and contains descendants, proceed to 2F.i.
// AD-HOC: We dont implement this step here — because is essentially unreachable code in the spec algorithm.
// We could never get here without descending through every subtree of an element whose Accessible Name or
// Accessible Description is being computed. And in our implementation of substep F about, were anyway already
// recursively descending through all the child nodes of every element whose Accessible Name or Accessible
// Description is being computed, in a way that never leads to this substep H every being hit.
// I. Otherwise, if the current node has a Tooltip attribute, return its value.
// https://www.w3.org/TR/accname-1.2/#dfn-tooltip-attribute

View file

@ -0,0 +1,15 @@
Summary
Harness status: OK
Rerun
Found 5 tests
5 Pass
Details
Result Test Name MessagePass button containing a rendered, unreferenced element that is aria-hidden=true, an unreferenced element with the hidden host language attribute, and an unreferenced element that is unconditionally rendered
Pass button labelled by element that is aria-hidden=true
Pass button labelled by element with the hidden host language attribute
Pass link labelled by elements with assorted visibility and a11y tree exposure
Pass heading with name from content, containing element that is visibility:hidden with nested content that is visibility:visible

View file

@ -0,0 +1,37 @@
Summary
Harness status: OK
Rerun
Found 27 tests
27 Pass
Details
Result Test Name MessagePass button with aria-labelledby using display:none hidden span (with nested span)
Pass button with aria-labelledby using display:none hidden span (with nested spans, depth 2)
Pass button with aria-labelledby using span without display:none (with nested display:none spans, depth 2)
Pass button with aria-labelledby using display:none hidden span (with nested sibling spans)
Pass button with aria-labelledby using span without display:none (with nested display:none sibling spans)
Pass button with aria-labelledby using span with display:none (with nested display:inline sibling spans)
Pass button with aria-labelledby using visibility:hidden span (with nested span)
Pass button with aria-labelledby using visibility:hidden span (with nested spans, depth 2)
Pass button with aria-labelledby using span without visibility:hidden (with nested visibility:hidden spans, depth 2)
Pass button with aria-labelledby using visibility:hidden hidden span (with nested sibling spans)
Pass button with aria-labelledby using span without visibility:hidden (with nested visibility:hidden sibling spans)
Pass button with aria-labelledby using span with visibility:hidden (with nested visibility:visible sibling spans)
Pass button with aria-labelledby using visibility:collapse span (with nested span)
Pass button with aria-labelledby using visibility:collapse span (with nested spans, depth 2)
Pass button with aria-labelledby using span without visibility:collapse (with nested visibility:visible spans, depth 2)
Pass button with aria-labelledby using visibility:collapse span (with nested sibling spans)
Pass button with aria-labelledby using span without visibility:collapse (with nested visibility:collapse sibling spans)
Pass button with aria-labelledby using span with visibility:collapse (with nested visible sibling spans)
Pass button with aria-labelledby using aria-hidden span (with nested span)
Pass button with aria-labelledby using aria-hidden span (with nested spans, depth 2)
Pass button with aria-labelledby using span without aria-hidden (with nested aria-hidden spans, depth 2)
Pass button with aria-labelledby using aria-hidden hidden span (with nested sibling spans)
Pass button with aria-labelledby using HTML5 hidden span (with nested span)
Pass button with aria-labelledby using HTML5 hidden span (with nested spans, depth 2)
Pass button with aria-labelledby using span without HTML5 hidden (with nested HTML5 hidden spans, depth 2)
Pass button with aria-labelledby using HTML5 hidden span (with nested hidden sibling spans)
Pass button with aria-labelledby using span without HTML5 hidden (with nested hidden sibling spans)

View file

@ -0,0 +1,92 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Name Comp: Hidden Not Referenced</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_hidden_not_referenced">#comp_hidden_not_referenced</a> portions of the AccName <em>Name Computation</em> algorithm.</p>
<button
class="ex"
data-expectedlabel="visible to all users"
data-testname="button containing a rendered, unreferenced element that is aria-hidden=true, an unreferenced element with the hidden host language attribute, and an unreferenced element that is unconditionally rendered"
>
<span aria-hidden="true">hidden,</span>
<span hidden>hidden from all users,</span>
<span>visible to all users</span>
</button>
<button
class="ex"
data-expectedlabel="hidden but referenced,"
data-testname="button labelled by element that is aria-hidden=true"
aria-labelledby="button-label-2"
>
<span aria-hidden="true" id="button-label-2">hidden but referenced,</span>
<span hidden>hidden from all users,</span>
<span>visible to all users</span>
</button>
<button
class="ex"
data-expectedlabel="hidden from all users but referenced,"
data-testname="button labelled by element with the hidden host language attribute"
aria-labelledby="button-label-3"
>
<span aria-hidden="true">hidden,</span>
<span hidden id="button-label-3">hidden from all users but referenced,</span>
<span>visible to all users</span>
</button>
<a
class="ex"
data-testname="link labelled by elements with assorted visibility and a11y tree exposure"
data-expectedlabel="visible to all users, hidden but referenced, hidden from all users but referenced"
href="#"
aria-labelledby="link-label-1a link-label-1b link-label-1c"
>
<span id="link-label-1a">
<span>visible to all users,</span>
<span aria-hidden="true">hidden,</span>
</span>
<span aria-hidden="true" id="link-label-1b">hidden but referenced,</span>
<span hidden id="link-label-1c">hidden from all users but referenced</span>
</a>
<h2
class="ex"
data-testname="heading with name from content, containing element that is visibility:hidden with nested content that is visibility:visible"
data-expectedlabel="visible to all users, un-hidden for all users"
>
visible to all users,
<span style="visibility: hidden;">
hidden from all users,
<span style="visibility: visible;">un-hidden for all users</span>
</span>
</h2>
<!-- TODO: Test cases once https://github.com/w3c/aria/issues/1256 resolved: -->
<!-- - button labelled by an element that is aria-hidden=true which contains a nested child that is aria-hidden=false -->
<!-- - button labelled by an element that is aria-hidden=false which belongs to a parent that is aria-hidden=true -->
<!-- - heading with name from content, containing rendered content that is aria-hidden=true with nested, rendered content that is aria-hidden=false -->
<!-- - heading with name from content, containing element with the hidden host language attribute with nested content that is aria-hidden=false -->
<!-- TODO: New test case?
<!-- What is the expectation for a details element when its given an -->
<!-- explicit role that allows name from contents (e.g., `comment`) -->
<!-- but is also not in the open state, and therefore has contents -->
<!-- that are both not rendered and excluded from the a11y tree. -->
<script>
AriaUtils.verifyLabelsBySelector(".ex");
</script>
</body>
</html>

View file

@ -0,0 +1,245 @@
<!doctype html>
<html>
<head>
<title>Name Comp: Labelledby & Hidden Nodes</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 hidden node name computation as part of the <a href="https://w3c.github.io/accname/#comp_labelledby">#comp_labelledby</a> portion of the AccName <em>Name Computation</em> algorithm.</p>
<!--
These tests verify browser conformance with the following note as part of accName computation Step 2B:
"The result of LabelledBy Recursion in combination with Hidden Not Referenced means
that user agents MUST include all nodes in the subtree as part of
the accessible name or accessible description, when the node referenced
by aria-labelledby or aria-describedby is hidden."
-->
<h2>Testing with <code>display:none</code></h2>
<button aria-labelledby="a11" data-expectedlabel="foo bar" data-testname="button with aria-labelledby using display:none hidden span (with nested span)" class="ex">x</button>
<span id="a11" style="display: none;">
foo
<span id="a12">bar</span>
</span>
<button aria-labelledby="a21" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using display:none hidden span (with nested spans, depth 2)" class="ex">x</button>
<span id="a21" style="display: none;">
foo
<span id="a22">
bar
<span id="a23">baz</span>
</span>
</span>
<button aria-labelledby="a31" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without display:none (with nested display:none spans, depth 2)" class="ex">x</button>
<span id="a31">
foo
<span id="a32" style="display: none;">
bar
<span id="a33">baz</span>
</span>
</span>
<button aria-labelledby="a41" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using display:none hidden span (with nested sibling spans)" class="ex">x</button>
<span id="a41" style="display: none;">
foo
<span id="a42">bar</span>
<span id="a43">baz</span>
</span>
<button aria-labelledby="a51" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without display:none (with nested display:none sibling spans)" class="ex">x</button>
<span id="a51">
foo
<span id="a52" style="display: none;">bar</span>
<span id="a53" style="display: none;">baz</span>
</span>
<button aria-labelledby="a61" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using span with display:none (with nested display:inline sibling spans)" class="ex">x</button>
<span id="a61" style="display: none;">
foo
<span id="a62" style="display: inline;">bar</span>
<span id="a63" style="display: inline;">baz</span>
</span>
<h2>Testing with <code>visibility:hidden</code></h2>
<button aria-labelledby="b11" data-expectedlabel="foo bar" data-testname="button with aria-labelledby using visibility:hidden span (with nested span)" class="ex">x</button>
<span id="b11" style="visibility: hidden;">
foo
<span id="b12">bar</span>
</span>
<button aria-labelledby="b21" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using visibility:hidden span (with nested spans, depth 2)" class="ex">x</button>
<span id="b21" style="visibility: hidden;">
foo
<span id="b22">
bar
<span id="b23">baz</span>
</span>
</span>
<button aria-labelledby="b31" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without visibility:hidden (with nested visibility:hidden spans, depth 2)" class="ex">x</button>
<span id="b31">
foo
<span id="b32" style="visibility: hidden;">
bar
<span id="b33">baz</span>
</span>
</span>
<button aria-labelledby="b41" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using visibility:hidden hidden span (with nested sibling spans)" class="ex">x</button>
<span id="b41" style="visibility: hidden;">
foo
<span id="b42">bar</span>
<span id="b43">baz</span>
</span>
<button aria-labelledby="b51" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without visibility:hidden (with nested visibility:hidden sibling spans)" class="ex">x</button>
<span id="b51">
foo
<span id="b52" style="visibility: hidden;">bar</span>
<span id="b53" style="visibility: hidden;">baz</span>
</span>
<button aria-labelledby="b61" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using span with visibility:hidden (with nested visibility:visible sibling spans)" class="ex">x</button>
<span id="b61" style="visibility: hidden;">
foo
<span id="b62" style="visibility: visible;">bar</span>
<span id="b63" style="visibility: visible;">baz</span>
</span>
<h2>Testing with <code>visibility:collapse</code></h2>
<button aria-labelledby="c11" data-expectedlabel="foo bar" data-testname="button with aria-labelledby using visibility:collapse span (with nested span)" class="ex">x</button>
<span id="c11" style="visibility: collapse;">
foo
<span id="c12">bar</span>
</span>
<button aria-labelledby="c21" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using visibility:collapse span (with nested spans, depth 2)" class="ex">x</button>
<span id="c21" style="visibility: collapse;">
foo
<span id="c22">
bar
<span id="c23">baz</span>
</span>
</span>
<button aria-labelledby="c31" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using span without visibility:collapse (with nested visibility:visible spans, depth 2)" class="ex">x</button>
<span id="c31">
foo
<span id="c32" style="visibility: visible;">
bar
<span id="c33" style="visibility: visible;">baz</span>
</span>
</span>
<button aria-labelledby="c41" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using visibility:collapse span (with nested sibling spans)" class="ex">x</button>
<span id="c41" style="visibility: collapse;">
foo
<span id="c42">bar</span>
<span id="c43">baz</span>
</span>
<button aria-labelledby="c51" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without visibility:collapse (with nested visibility:collapse sibling spans)" class="ex">x</button>
<span id="c51">
foo
<span id="c52" style="visibility: collapse;">bar</span>
<span id="c53" style="visibility: collapse;">baz</span>
</span>
<button aria-labelledby="c61" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using span with visibility:collapse (with nested visible sibling spans)" class="ex">x</button>
<span id="c61" style="visibility: collapse;">
foo
<span id="c62" style="visibility: visible;">bar</span>
<span id="c63" style="visibility: visible;">baz</span>
</span>
<h2>Testing with <code>aria-hidden</code></h2>
<button aria-labelledby="d11" data-expectedlabel="foo bar" data-testname="button with aria-labelledby using aria-hidden span (with nested span)" class="ex">x</button>
<span id="d11" aria-hidden="true">
foo
<span id="d12">bar</span>
</span>
<button aria-labelledby="d21" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using aria-hidden span (with nested spans, depth 2)" class="ex">x</button>
<span id="d21" aria-hidden="true">
foo
<span id="d22">
bar
<span id="d23">baz</span>
</span>
</span>
<button aria-labelledby="d31" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without aria-hidden (with nested aria-hidden spans, depth 2)" class="ex">x</button>
<span id="d31">
foo
<span id="d32" aria-hidden="true">
bar
<span id="d33">baz</span>
</span>
</span>
<button aria-labelledby="d41" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using aria-hidden hidden span (with nested sibling spans)" class="ex">x</button>
<span id="d41" aria-hidden="true">
foo
<span id="d42">bar</span>
<span id="d43">baz</span>
</span>
<h2>Testing with <code>hidden</code> attribute</h2>
<button aria-labelledby="e11" data-expectedlabel="foo bar" data-testname="button with aria-labelledby using HTML5 hidden span (with nested span)" class="ex">x</button>
<span id="e11" hidden>
foo
<span id="e12">bar</span>
</span>
<button aria-labelledby="e21" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using HTML5 hidden span (with nested spans, depth 2)" class="ex">x</button>
<span id="e21" hidden>
foo
<span id="e22">
bar
<span id="e23">baz</span>
</span>
</span>
<button aria-labelledby="e31" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without HTML5 hidden (with nested HTML5 hidden spans, depth 2)" class="ex">x</button>
<span id="e31">
foo
<span id="e32" hidden>
bar
<span id="e33">baz</span>
</span>
</span>
<button aria-labelledby="e41" data-expectedlabel="foo bar baz" data-testname="button with aria-labelledby using HTML5 hidden span (with nested hidden sibling spans)" class="ex">x</button>
<span id="e41" hidden>
foo
<span id="e42">bar</span>
<span id="e43">baz</span>
</span>
<button aria-labelledby="e51" data-expectedlabel="foo" data-testname="button with aria-labelledby using span without HTML5 hidden (with nested hidden sibling spans)" class="ex">x</button>
<span id="e51">
foo
<span id="e52" hidden>bar</span>
<span id="e53" hidden>baz</span>
</span>
<script>
AriaUtils.verifyLabelsBySelector(".ex");
</script>
</body>
</html>