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
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 // 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 // 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. // 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) if (should_compute_role == ShouldComputeRole::Yes)
role = element->role_from_role_attribute_value(); 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 // Per-spec, at https://w3c.github.io/html-aam/#el-aside and elsewhere, computing a default role for certain
// default role for an aside element or section element requires first computing its accessible name — that is, // elements (img, aside, and section) requires first computing its accessible name. So to avoid getting into an
// calling into this name_or_description code. But if we then try to determine a default role for the aside // infinite loop here, the callers in our code for those cases pass in ShouldComputeRole::Yes.
// 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.
// https://github.com/w3c/aria/issues/2391 // 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(); role = element->default_role();
// 2. Compute the text alternative for the current node: // 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); 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 // 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 // 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 // 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 // https://w3c.github.io/html-aam/#img-element-accessible-name-computation
// use alt attribute, even if its value is the empty string. // 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)) if (is<HTML::HTMLImageElement>(*element) && element->has_attribute(HTML::AttributeNames::alt))
return element->get_attribute(HTML::AttributeNames::alt).value(); 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 // https://w3c.github.io/html-aam/#el-aside
for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { 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) 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; return ARIA::Role::generic;
} }
// https://w3c.github.io/html-aam/#el-aside-ancestorbodymain // 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 // https://www.w3.org/TR/html-aria/#el-section
if (local_name() == TagNames::section) { if (local_name() == TagNames::section) {
// role=region if the section element has an accessible name // 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; return ARIA::Role::region;
// Otherwise, role=generic // Otherwise, role=generic
return ARIA::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 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
// https://www.w3.org/TR/html-aria/#el-img-no-alt // https://www.w3.org/TR/html-aria/#el-img-no-alt
// https://w3c.github.io/aria/#image // 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 // 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"). // https://wpt.fyi/results/html-aam/roles.html expects the value to be "image" (not "img").
if (!alt().is_empty())
return ARIA::Role::image; 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;
} }
// https://html.spec.whatwg.org/multipage/images.html#use-srcset-or-picture // https://html.spec.whatwg.org/multipage/images.html#use-srcset-or-picture

View file

@ -1,8 +1,9 @@
Harness status: OK Harness status: OK
Found 19 tests Found 48 tests
19 Pass 47 Pass
1 Fail
Pass el-a Pass el-a
Pass el-aside Pass el-aside
Pass el-aside-in-main 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-name
Pass el-aside-in-nav-with-role Pass el-aside-in-nav-with-role
Pass el-aside-in-section-with-name 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-footer-ancestorbody
Pass el-header-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
Pass el-section-aria-labelledby
Pass el-section-title
Pass el-a-no-href Pass el-a-no-href
Pass el-aside-in-article-in-main Pass el-aside-in-article-in-main
Pass el-aside-in-article Pass el-aside-in-article
Pass el-aside-in-aside Pass el-aside-in-aside
Pass el-aside-in-nav Pass el-aside-in-nav
Pass el-aside-in-section Pass el-aside-in-section
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-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

@ -50,6 +50,15 @@
<section aria-label="x"> <section aria-label="x">
<aside data-testname="el-aside-in-section" class="ex-generic">x</aside> <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-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> </section>
<!-- el-footer --> <!-- el-footer -->
@ -62,10 +71,38 @@
<header data-testname="el-header-ancestorbody" data-expectedrole="banner" class="ex">x</header> <header data-testname="el-header-ancestorbody" data-expectedrole="banner" class="ex">x</header>
<!-- main>header -> ./roles-contextual.tentative.html --> <!-- 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 --> <!-- el-section -->
<section data-testname="el-section" aria-label="x" data-expectedrole="region" class="ex">x</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-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> <script>
AriaUtils.verifyRolesBySelector(".ex"); AriaUtils.verifyRolesBySelector(".ex");