From 68894306e2c31beee0dbf05a26bfecdee3541f0e Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Wed, 4 Dec 2024 21:55:39 +0900 Subject: [PATCH] LibWeb: Compute default ARIA roles context-sensitively where required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change implements spec-conformant computation of default ARIA roles for elements whose expected default role depends on the element’s context — specifically, either on the element’s ancestry, or on whether the element has an accessible name, or both. This affects the “aside”, “footer”, “header”, and “section” elements. Otherwise, without this change, “aside”, “footer”, “header”, and “section” elements may unexpectedly end up with the wrong default roles. --- Libraries/LibWeb/DOM/Node.cpp | 12 ++- Libraries/LibWeb/HTML/HTMLElement.cpp | 42 ++++++---- .../wpt-import/html-aam/roles-contextual.txt | 24 ++++++ .../wpt-import/html-aam/roles-contextual.html | 76 +++++++++++++++++++ 4 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.html diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index a455f7f8976..106aed60edd 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -2228,7 +2228,17 @@ ErrorOr Node::name_or_description(NameOrDescription target, Document con if (is_element()) { auto const* element = static_cast(this); - auto role = element->role_or_default(); + auto 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, that’d 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 + // isn’t an aside element or section element. + // https://github.com/w3c/aria/issues/2391 + if (!role.has_value() && element->local_name() != HTML::TagNames::aside && element->local_name() != HTML::TagNames::section) + role = element->default_role(); + // 2. Compute the text alternative for the current node: // A. Hidden Not Referenced: If the current node is hidden and is: diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index c7d1f0fdf37..1aabe358738 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -734,8 +734,16 @@ Optional HTMLElement::default_role() const if (local_name() == TagNames::article) return ARIA::Role::article; // https://www.w3.org/TR/html-aria/#el-aside - if (local_name() == TagNames::aside) + if (local_name() == TagNames::aside) { + // https://w3c.github.io/html-aam/#el-aside + for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { + if (first_is_one_of(ancestor->local_name(), TagNames::article, TagNames::aside, TagNames::nav, TagNames::section) + && accessible_name(document()).value().is_empty()) + return ARIA::Role::generic; + } + // https://w3c.github.io/html-aam/#el-aside-ancestorbodymain return ARIA::Role::complementary; + } // https://www.w3.org/TR/html-aria/#el-b if (local_name() == TagNames::b) return ARIA::Role::generic; @@ -758,16 +766,22 @@ Optional HTMLElement::default_role() const if (local_name() == TagNames::figure) return ARIA::Role::figure; // https://www.w3.org/TR/html-aria/#el-footer - if (local_name() == TagNames::footer) { - // TODO: If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=contentinfo - // Otherwise, role=generic - return ARIA::Role::generic; - } // https://www.w3.org/TR/html-aria/#el-header - if (local_name() == TagNames::header) { - // TODO: If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=banner - // Otherwise, role=generic - return ARIA::Role::generic; + if (local_name() == TagNames::footer || local_name() == TagNames::header) { + // If not a descendant of an article, aside, main, nav or section element, or an element with role=article, + // complementary, main, navigation or region then (footer) role=contentinfo (header) role=banner. Otherwise, + // role=generic. + for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { + if (first_is_one_of(ancestor->local_name(), TagNames::article, TagNames::aside, TagNames::main, TagNames::nav, TagNames::section)) + return ARIA::Role::generic; + if (first_is_one_of(ancestor->role_or_default(), ARIA::Role::article, ARIA::Role::complementary, ARIA::Role::main, ARIA::Role::navigation, ARIA::Role::region)) + return ARIA::Role::generic; + } + // then (footer) role=contentinfo. + if (local_name() == TagNames::footer) + return ARIA::Role::contentinfo; + // (header) role=banner + return ARIA::Role::banner; } // https://www.w3.org/TR/html-aria/#el-hgroup if (local_name() == TagNames::hgroup) @@ -792,9 +806,11 @@ Optional HTMLElement::default_role() const return ARIA::Role::search; // https://www.w3.org/TR/html-aria/#el-section if (local_name() == TagNames::section) { - // TODO: role=region if the section element has an accessible name - // Otherwise, no corresponding role - return ARIA::Role::region; + // role=region if the section element has an accessible name + if (!accessible_name(document()).value().is_empty()) + return ARIA::Role::region; + // Otherwise, role=generic + return ARIA::Role::generic; } // https://www.w3.org/TR/html-aria/#el-small if (local_name() == TagNames::small) diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.txt new file mode 100644 index 00000000000..d272dff7495 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.txt @@ -0,0 +1,24 @@ +Harness status: OK + +Found 19 tests + +19 Pass +Pass el-a +Pass el-aside +Pass el-aside-in-main +Pass el-aside-in-article-in-main-with-name +Pass el-aside-in-article-with-name +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-footer-ancestorbody +Pass el-header-ancestorbody +Pass el-section +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 \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.html new file mode 100644 index 00000000000..5fd897c1e62 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.html @@ -0,0 +1,76 @@ + + + + HTML-AAM Contextual-Specific Role Verification Tests + + + + + + + + + + +

Tests contextual computedrole mappings defined in HTML-AAM, 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.

+ +

These should remain in alphabetical order.

+ + + +x +x + + + +
+ +
+ + +
+
+
+ + +
+ + + +
+ + +
+ + + +
x
+ + + + +
x
+ + + +
x
+
x
+ + + + + + \ No newline at end of file