LibWeb: Update ARIA default-role handling for img, section, and aside

This change updates our handling of computation for default ARIA roles
for the img, section, and aside elements to match the expectations in
the updated WPT test at http://wpt.live/html-aam/roles-contextual.html.
This commit is contained in:
sideshowbarker 2025-03-25 18:59:18 +09:00
parent 6b9e8cf40c
commit fdcb27749d
No known key found for this signature in database
5 changed files with 90 additions and 22 deletions

View file

@ -2470,16 +2470,14 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// then try to retrieve a role for such elements here, thatd then end up calling right back into this
// name_or_description code — which would cause the calls to loop infinitely. So to avoid that, the caller
// in the ARIAMixin code can pass the shouldComputeRole parameter to indicate we must skip the role lookup.
// https://github.com/w3c/aria/issues/2404
if (should_compute_role == ShouldComputeRole::Yes)
role = element->role_from_role_attribute_value();
// Per https://w3c.github.io/html-aam/#el-aside and https://w3c.github.io/html-aam/#el-section, computing a
// default role for an aside element or section element requires first computing its accessible name — that is,
// calling into this name_or_description code. But if we then try to determine a default role for the aside
// element or section element here, thatd then end up calling right back into this name_or_description code —
// which would cause the calls to loop infinitely. So to avoid that, we only compute a default role here if this
// isnt an aside element or section element.
// Per-spec, at https://w3c.github.io/html-aam/#el-aside and elsewhere, computing a default role for certain
// elements (img, aside, and section) requires first computing its accessible name. So to avoid getting into an
// infinite loop here, the callers in our code for those cases pass in ShouldComputeRole::Yes.
// https://github.com/w3c/aria/issues/2391
if (!role.has_value() && element->local_name() != HTML::TagNames::aside && element->local_name() != HTML::TagNames::section)
if (should_compute_role == ShouldComputeRole::Yes && !role.has_value())
role = element->default_role();
// 2. Compute the text alternative for the current node:
@ -2550,7 +2548,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
total_accumulated_text.append(result);
}
// iii. Return the accumulated text.
// iii. Return the accumulated text if it is not the empty string ("").
// 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
@ -2675,7 +2673,9 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
//
// https://w3c.github.io/html-aam/#img-element-accessible-name-computation
// use alt attribute, even if its value is the empty string.
// See also https://wpt.fyi/results/accname/name/comp_tooltip.tentative.html.
// See also https://wpt.fyi/results/accname/name/comp_tooltip.tentative.html — but also see
// https://github.com/w3c/aria/issues/2491 and the el-img-empty-alt-title subtest in the test at
// https://wpt.fyi/results/html-aam/roles-contextual.html.
if (is<HTML::HTMLImageElement>(*element) && element->has_attribute(HTML::AttributeNames::alt))
return element->get_attribute(HTML::AttributeNames::alt).value();

View file

@ -845,7 +845,7 @@ Optional<ARIA::Role> HTMLElement::default_role() const
// https://w3c.github.io/html-aam/#el-aside
for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) {
if (ancestor->local_name().is_one_of(TagNames::article, TagNames::aside, TagNames::nav, TagNames::section)
&& accessible_name(document()).value().is_empty())
&& accessible_name(document(), DOM::ShouldComputeRole::No).value().trim_whitespace(TrimMode::Both).value().is_empty())
return ARIA::Role::generic;
}
// https://w3c.github.io/html-aam/#el-aside-ancestorbodymain
@ -932,7 +932,7 @@ Optional<ARIA::Role> HTMLElement::default_role() const
// https://www.w3.org/TR/html-aria/#el-section
if (local_name() == TagNames::section) {
// role=region if the section element has an accessible name
if (!accessible_name(document()).value().is_empty())
if (!accessible_name(document(), DOM::ShouldComputeRole::No).value().trim_whitespace(TrimMode::Both).value().is_empty())
return ARIA::Role::region;
// Otherwise, role=generic
return ARIA::Role::generic;

View file

@ -418,17 +418,18 @@ WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> HTMLImageElement::decode() const
Optional<ARIA::Role> HTMLImageElement::default_role() const
{
// https://www.w3.org/TR/html-aria/#el-img-empty-alt
// NOTE: The "none" role value is a synonym for the older "presentation" role value; however, the el-img-alt-no-value
// test in https://wpt.fyi/results/html-aam/roles.html expects the value to be "none" (not "presentation").
if (has_attribute(HTML::AttributeNames::alt) && alt().is_empty()
&& accessible_name(document(), DOM::ShouldComputeRole::No).value().trim_whitespace(TrimMode::Both).value().is_empty())
return ARIA::Role::none;
// https://www.w3.org/TR/html-aria/#el-img
// https://www.w3.org/TR/html-aria/#el-img-no-alt
// https://w3c.github.io/aria/#image
// NOTE: The "image" role value is a synonym for the older "img" role value; however, the el-img test in
// https://wpt.fyi/results/html-aam/roles.html expects the value to be "image" (not "img").
if (!alt().is_empty())
return ARIA::Role::image;
// https://www.w3.org/TR/html-aria/#el-img-empty-alt
// NOTE: The "none" role value is a synonym for the older "presentation" role value; however, the el-img-alt-no-value
// test in https://wpt.fyi/results/html-aam/roles.html expects the value to be "none" (not "presentation").
return ARIA::Role::none;
return ARIA::Role::image;
}
// https://html.spec.whatwg.org/multipage/images.html#use-srcset-or-picture

View file

@ -1,8 +1,9 @@
Harness status: OK
Found 19 tests
Found 48 tests
19 Pass
47 Pass
1 Fail
Pass el-a
Pass el-aside
Pass el-aside-in-main
@ -12,13 +13,42 @@ Pass el-aside-in-aside-with-name
Pass el-aside-in-nav-with-name
Pass el-aside-in-nav-with-role
Pass el-aside-in-section-with-name
Pass el-aside-in-section-aria-labelledby
Pass el-aside-in-section-title
Pass el-footer-ancestorbody
Pass el-header-ancestorbody
Pass el-img-no-name
Pass el-img-empty-alt-aria-label
Pass el-img-empty-alt-aria-labelledby
Fail el-img-empty-alt-title
Pass el-section
Pass el-section-aria-labelledby
Pass el-section-title
Pass el-a-no-href
Pass el-aside-in-article-in-main
Pass el-aside-in-article
Pass el-aside-in-aside
Pass el-aside-in-nav
Pass el-aside-in-section
Pass el-section-no-name
Pass el-aside-in-section-aria-label-empty
Pass el-aside-in-section-aria-label-whitespace
Pass el-aside-in-section-aria-labelledby-non-existing
Pass el-aside-in-section-aria-labelledby-empty
Pass el-aside-in-section-aria-labelledby-whitespace
Pass el-aside-in-section-title-empty
Pass el-aside-in-section-title-whitespace
Pass el-img-empty-alt-aria-label-empty
Pass el-img-empty-alt-aria-label-whitespace
Pass el-img-empty-alt-aria-labelledby-non-existing
Pass el-img-empty-alt-aria-labelledby-empty
Pass el-img-empty-alt-aria-labelledby-whitespace
Pass el-img-empty-alt-title-empty
Pass el-img-empty-alt-title-whitespace
Pass el-section-no-name
Pass el-section-aria-label-empty
Pass el-section-aria-label-whitespace
Pass el-section-aria-labelledby-non-existing
Pass el-section-aria-labelledby-empty
Pass el-section-aria-labelledby-whitespace
Pass el-section-title-empty
Pass el-section-title-whitespace

View file

@ -12,7 +12,7 @@
<body>
<p>Tests contextual computedrole mappings defined in <a href="https://w3c.github.io/html-aam/">HTML-AAM</a>, where the returned computed role is expected to change based on the context. Most test names correspond to a unique ID defined in the spec.<p>
<p>Tests contextual computed role mappings defined in <a href="https://w3c.github.io/html-aam/">HTML-AAM</a>, where the returned computed role is expected to change based on the context. Most test names correspond to a unique ID defined in the spec.<p>
<p>These should remain in alphabetical order.</code></p>
@ -50,6 +50,15 @@
<section aria-label="x">
<aside data-testname="el-aside-in-section" class="ex-generic">x</aside>
<aside data-testname="el-aside-in-section-with-name" data-expectedrole="complementary" aria-label="x" class="ex">x</aside>
<aside data-testname="el-aside-in-section-aria-label-empty" class="ex-generic" aria-label="">x</aside>
<aside data-testname="el-aside-in-section-aria-label-whitespace" class="ex-generic" aria-label=" ">x</aside>
<aside data-testname="el-aside-in-section-aria-labelledby" data-expectedrole="complementary" class="ex" aria-labelledby="labelledby">x</aside>
<aside data-testname="el-aside-in-section-aria-labelledby-non-existing" class="ex-generic" aria-labelledby="non-existing">x</aside>
<aside data-testname="el-aside-in-section-aria-labelledby-empty" class="ex-generic" aria-labelledby="empty">x</aside>
<aside data-testname="el-aside-in-section-aria-labelledby-whitespace" class="ex-generic" aria-labelledby="space">x</aside>
<aside data-testname="el-aside-in-section-title" data-expectedrole="complementary" title="x" class="ex">x</aside>
<aside data-testname="el-aside-in-section-title-empty" class="ex-generic" title="">x</aside>
<aside data-testname="el-aside-in-section-title-whitespace" class="ex-generic" title=" ">x</aside>
</section>
<!-- el-footer -->
@ -62,10 +71,38 @@
<header data-testname="el-header-ancestorbody" data-expectedrole="banner" class="ex">x</header>
<!-- main>header -> ./roles-contextual.tentative.html -->
<!-- el-img-empty-alt -->
<img data-testname="el-img-no-name" data-expectedrole="image" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==">
<!-- img empty alt -> ./roles.html -->
<img data-testname="el-img-empty-alt-aria-label" data-expectedrole="image" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-label="x">
<img data-testname="el-img-empty-alt-aria-label-empty" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-label="">
<img data-testname="el-img-empty-alt-aria-label-whitespace" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-label=" ">
<img data-testname="el-img-empty-alt-aria-labelledby" data-expectedrole="image" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-labelledby="labelledby">
<img data-testname="el-img-empty-alt-aria-labelledby-non-existing" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-labelledby="non-existing">
<img data-testname="el-img-empty-alt-aria-labelledby-empty" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-labelledby="empty">
<img data-testname="el-img-empty-alt-aria-labelledby-whitespace" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt aria-labelledby="space">
<img data-testname="el-img-empty-alt-title" data-expectedrole="image" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt title="x">
<img data-testname="el-img-empty-alt-title-empty" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt title="">
<img data-testname="el-img-empty-alt-title-whitespace" class="ex-generic" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt title=" ">
<!-- el-section -->
<section data-testname="el-section" aria-label="x" data-expectedrole="region" class="ex">x</section>
<section data-testname="el-section-no-name" class="ex-generic">x</section>
<section data-testname="el-section-aria-label-empty" class="ex-generic" aria-label="">x</section>
<section data-testname="el-section-aria-label-whitespace" class="ex-generic" aria-label=" ">x</section>
<section data-testname="el-section-aria-labelledby" data-expectedrole="region" class="ex" aria-labelledby="labelledby">x</section>
<section data-testname="el-section-aria-labelledby-non-existing" class="ex-generic" aria-labelledby="non-existing">x</section>
<section data-testname="el-section-aria-labelledby-empty" class="ex-generic" aria-labelledby="empty">x</section>
<section data-testname="el-section-aria-labelledby-whitespace" class="ex-generic" aria-labelledby="space">x</section>
<section data-testname="el-section-title" data-expectedrole="region" title="x" class="ex">x</section>
<section data-testname="el-section-title-empty" class="ex-generic" title="">x</section>
<section data-testname="el-section-title-whitespace" class="ex-generic" title=" ">x</section>
<!-- element to reference for aria-labelledby tests -->
<div id="labelledby">labelledby</div>
<div id="empty"></div>
<div id="space"> </div>
<script>
AriaUtils.verifyRolesBySelector(".ex");
@ -73,4 +110,4 @@ AriaUtils.verifyGenericRolesBySelector(".ex-generic");
</script>
</body>
</html>
</html>