LibWeb: Fix accessible-name computation for “tooltip” & empty-alt cases

This change fixes accessible-name computation for:

- an element that has empty text content but that also has a title
  attribute (“tooltip”) with a non-empty value
- an img element whose alt attribute is the empty string but that also
  has a “title” attribute with a non-empty value

Otherwise, without this change, the accessible name unexpectedly isn’t
computed correctly for those cases.
This commit is contained in:
sideshowbarker 2024-11-07 20:33:43 +09:00 committed by Tim Flynn
parent 4ae1bca67d
commit 3594cdf948
Notes: github-actions[bot] 2024-11-09 13:09:02 +00:00
3 changed files with 122 additions and 5 deletions

View file

@ -0,0 +1,33 @@
Summary
Harness status: OK
Rerun
Found 23 tests
23 Pass
Details
Result Test Name MessagePass link with img with tooltip label
Pass link with text with tooltip label and no contents
Pass link with text with tooltip label and contents
Pass div with text with tooltip label
Pass img with tooltip label without alt
Pass img with tooltip label with empty alt
Pass img with tooltip label with alt
Pass img with tooltip label without title
Pass select with tooltip label
Pass button with tooltip label
Pass checkbox input with tooltip label
Pass radio input with tooltip label
Pass text input with placeholder and tooltip label
Pass password input with placeholder and tooltip label
Pass number input with placeholder and tooltip label
Pass search input with placeholder and tooltip label
Pass tel input with placeholder and tooltip label
Pass email input with placeholder and tooltip label
Pass url input with placeholder and tooltip label
Pass textarea with placeholder and tooltip label
Pass abbr with tooltip label
Pass summary with tooltip label and contents
Pass iframe with tooltip label

View file

@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<title>Name Comp: Tooltip</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_tooltip">#comp_tooltip</a> portions of the AccName <em>Name Computation</em> algorithm.</p>
<a href="#" title="title" data-expectedlabel="title" data-testname="link with img with tooltip label" class="ex"><img alt="" src="data:image/gif;base65,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="></a>
<a href="#" title="title" data-expectedlabel="title" data-testname="link with text with tooltip label and no contents" class="ex"></a>
<a href="#" title="title" data-expectedlabel="contents" data-testname="link with text with tooltip label and contents" class="ex">contents</a>
<div title="title" role="group" data-expectedlabel="title" data-testname="div with text with tooltip label" class="ex">contents</div><!-- Note: group role disallows nameFrom:contents -->
<img title="title" data-expectedlabel="title" data-testname="img with tooltip label without alt" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==">
<img title="title" data-expectedlabel="title" alt="" data-testname="img with tooltip label with empty alt" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==">
<img title="title" data-expectedlabel="alt" alt="alt" data-testname="img with tooltip label with alt" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==">
<img data-expectedlabel="alt" alt="alt" data-testname="img with tooltip label without title" class="ex" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==">
<select title="title" data-expectedlabel="title" data-testname="select with tooltip label" class="ex">
<option value="" disabled selected>select options</option>
<option value="1">option 1</option>
<option value="2">option 2</option>
<option value="3">option 3</option>
</select>
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<button title="title" data-expectedlabel="contents" data-testname="button with tooltip label" class="ex">contents</button>
<input type="checkbox" title="title" data-expectedlabel="title" data-testname="checkbox input with tooltip label" class="ex">
<input type="radio" title="title" data-expectedlabel="title" data-testname="radio input with tooltip label" class="ex">
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<!-- https://w3c.github.io/html-aam/#input-type-text-input-type-password-input-type-number-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-element-accessible-name-computation -->
<input type="text" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="text input with placeholder and tooltip label" class="ex">
<input type="password" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="password input with placeholder and tooltip label" class="ex">
<input type="number" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="number input with placeholder and tooltip label" class="ex">
<input type="search" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="search input with placeholder and tooltip label" class="ex">
<input type="tel" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="tel input with placeholder and tooltip label" class="ex">
<input type="email" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="email input with placeholder and tooltip label" class="ex">
<input type="url" title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="url input with placeholder and tooltip label" class="ex">
<textarea title="title" data-expectedlabel="title" placeholder="placeholder" data-testname="textarea with placeholder and tooltip label" class="ex"></textarea>
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<!-- https://w3c.github.io/html-aam/#text-level-element-accessible-name-computation -->
<abbr title="Web Platform Tests" data-expectedlabel="Web Platform Tests" data-testname="abbr with tooltip label" class="ex">WPT</abbr>
<!-- kbd test disabled: see resolution at https://github.com/web-platform-tests/interop-accessibility/issues/131 -->
<!-- <kbd title="Control + Option" data-expectedlabel="Control + Option" data-testname="kbd with tooltip label" class="ex">CTRL + OPT</kbd> -->
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<!-- https://w3c.github.io/html-aam/#summary-element-accessible-name-computation -->
<details>
<summary title="title" data-expectedlabel="contents" data-testname="summary with tooltip label and contents" class="ex">contents</summary>
details
</details>
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<!-- https://w3c.github.io/html-aam/#iframe-element-accessible-name-computation -->
<iframe title="title" data-expectedlabel="title" data-testname="iframe with tooltip label" width="20px" height="20px" class="ex"></iframe>
<script>
AriaUtils.verifyLabelsBySelector(".ex");
</script>
</body>
</html>

View file

@ -34,6 +34,7 @@
#include <LibWeb/DOM/StaticNodeList.h>
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/HTML/HTMLSelectElement.h>
#include <LibWeb/HTML/HTMLSlotElement.h>
@ -2188,6 +2189,7 @@ 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();
// 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
@ -2312,11 +2314,22 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// - Otherwise, return the value of aria-label.
return element->aria_label().value();
}
// TODO: D. Otherwise, if the current node's native markup provides an attribute (e.g. title) or element (e.g. HTML label) 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 (role="presentation" or role="none").
// 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
// (role="presentation" or role="none").
if (role != ARIA::Role::presentation && role != ARIA::Role::none) {
if (is<HTML::HTMLImageElement>(*element)) {
if (auto alt = element->get_attribute(HTML::AttributeNames::alt); alt.has_value())
return alt.release_value();
}
// TODO: Add handling for SVGTitleElement, and also confirm (through existing WPT test cases) whether
// HTMLLabelElement is already handled (by the code for step C. “Embedded Control” above) in conformance
// 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:
auto role = element->role_or_default();
if ((role.has_value() && ARIA::allows_name_from_content(role.value())) || is_descendant == IsDescendant::Yes) {
// i. Set the accumulated text to the empty string.
total_accumulated_text.clear();
@ -2348,8 +2361,9 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
return IterationDecision::Continue;
});
// iv. Return the accumulated text.
return total_accumulated_text.to_string();
// v. Return the accumulated text if it is not the empty string ("").
if (!total_accumulated_text.is_empty())
return total_accumulated_text.to_string();
// Important: Each node in the subtree is consulted only once. If text has been collected from a descendant, but is referenced by another IDREF in some descendant node, then that second, or subsequent, reference is not followed. This is done to avoid infinite loops.
}
}