Skip to content

Commit f96d509

Browse files
Joshua Hoffmanhoffmanjoshua
authored andcommitted
AX: <label>s that are targets of aria-labelledby lose their labelfor relationship when they change
https://bugs.webkit.org/show_bug.cgi?id=298891 rdar://158906980 Reviewed by Tyler Wilcock. When <label> elements change, the handleLabelChanged method of AXObjectCache get's called to update its labelfor relationships. In this code path, we clear all LabelFor relationships and re-add them based on the label's `for` attribute. This is fine when a control's label just uses the `for` attribute, but if the control itself uses aria-labelledby to point to the label element, we end up clearing this relationship. This causes us a control's label to be stale, for example, because a text change on a label won't know what other elements to update. Fix this by preventing clearing LabelFor relationships when the control uses aria-label or aria-labelledby. Test: accessibility/label-for-with-aria-labelledby.html Test: accessibility/label-for-with-aria-labelledby.html * LayoutTests/accessibility/label-for-with-aria-labelledby-expected.txt: Added. * LayoutTests/accessibility/label-for-with-aria-labelledby.html: Added. * LayoutTests/platform/glib/accessibility/label-for-with-aria-labelledby-expected.txt: Added. * Source/WebCore/accessibility/AXObjectCache.cpp: (WebCore::AXObjectCache::handleAttributeChange): (WebCore::hasAnyARIALabelling): (WebCore::AXObjectCache::handleLabelChanged): (WebCore::AXObjectCache::updateLabelFor): (WebCore::AXObjectCache::addRelation): * Source/WebCore/accessibility/AXObjectCache.h: Canonical link: https://commits.webkit.org/300029@main
1 parent 13855c6 commit f96d509

File tree

5 files changed

+88
-12
lines changed

5 files changed

+88
-12
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
This tests that the aria-labelledby pointing to a label works when the label has a `for` attribute.
2+
3+
AXTitle: Hello
4+
AXDescription: Hello
5+
AXHelp:
6+
Changing inner HTML of label:
7+
AXTitle: World
8+
AXDescription: World
9+
AXHelp:
10+
11+
PASS successfullyParsed is true
12+
13+
TEST COMPLETE
14+
World
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2+
<html>
3+
<head>
4+
<script src="../resources/accessibility-helper.js"></script>
5+
<script src="../resources/js-test.js"></script>
6+
</head>
7+
<body>
8+
9+
<input id="textbox" type="text" size=20 aria-labelledby="textbox-label">
10+
<label id="textbox-label" for="textbox">Hello</label>
11+
12+
<script>
13+
let output = "This tests that the aria-labelledby pointing to a label works when the label has a `for` attribute.\n\n";
14+
15+
var platformText;
16+
if (window.accessibilityController) {
17+
window.jsTestIsAsync = true;
18+
var textbox = accessibilityController.accessibleElementById("textbox");
19+
output += `${platformTextAlternatives(textbox)}\n`;
20+
21+
document.getElementById("textbox-label").innerHTML = "World";
22+
output += "Changing inner HTML of label:\n";
23+
setTimeout(async function() {
24+
await waitFor(() => {
25+
platformText = platformTextAlternatives(textbox);
26+
return platformText.includes("World");
27+
});
28+
output += `${platformTextAlternatives(textbox)}\n`;
29+
debug(output);
30+
finishJSTest();
31+
}, 0);
32+
}
33+
</script>
34+
</body>
35+
</html>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
This tests that the aria-labelledby pointing to a label works when the label has a `for` attribute.
2+
3+
AXTitle: Hello
4+
AXDescription:
5+
Changing inner HTML of label:
6+
AXTitle: World
7+
AXDescription:
8+
9+
PASS successfullyParsed is true
10+
11+
TEST COMPLETE
12+
World

Source/WebCore/accessibility/AXObjectCache.cpp

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2873,12 +2873,14 @@ void AXObjectCache::handleAttributeChange(Element* element, const QualifiedName&
28732873
postNotification(element, AXNotification::DisabledStateChanged);
28742874
else if (attrName == forAttr) {
28752875
if (RefPtr label = dynamicDowncast<HTMLLabelElement>(element)) {
2876-
updateLabelFor(*label);
2876+
bool updatedLabelFor = updateLabelFor(*label);
28772877

2878-
if (RefPtr oldControl = element->treeScope().elementByIdResolvingReferenceTarget(oldValue))
2879-
postNotification(oldControl.get(), AXNotification::TextChanged);
2880-
if (RefPtr newControl = element->treeScope().elementByIdResolvingReferenceTarget(newValue))
2881-
postNotification(newControl.get(), AXNotification::TextChanged);
2878+
if (updatedLabelFor) {
2879+
if (RefPtr oldControl = element->treeScope().elementByIdResolvingReferenceTarget(oldValue))
2880+
postNotification(oldControl.get(), AXNotification::TextChanged);
2881+
if (RefPtr newControl = element->treeScope().elementByIdResolvingReferenceTarget(newValue))
2882+
postNotification(newControl.get(), AXNotification::TextChanged);
2883+
}
28822884
}
28832885
} else if (attrName == requiredAttr)
28842886
postNotification(element, AXNotification::RequiredStatusChanged);
@@ -3106,16 +3108,25 @@ void AXObjectCache::handleAttributeChange(Element* element, const QualifiedName&
31063108
handleInputTypeChanged(*element);
31073109
}
31083110

3111+
static bool hasAnyARIALabelling(Element& element)
3112+
{
3113+
return element.hasAttributeWithoutSynchronization(aria_labelAttr)
3114+
|| element.hasAttributeWithoutSynchronization(aria_labelledbyAttr)
3115+
|| element.hasAttributeWithoutSynchronization(aria_labeledbyAttr);
3116+
}
3117+
31093118
void AXObjectCache::handleLabelChanged(AccessibilityObject* object)
31103119
{
31113120
AXTRACE("AXObjectCache::handleLabelChanged"_s);
31123121

31133122
if (!object)
31143123
return;
31153124

3125+
bool updatedLabelFor = false;
31163126
if (RefPtr label = dynamicDowncast<HTMLLabelElement>(object->element()))
3117-
updateLabelFor(*label);
3118-
else {
3127+
updatedLabelFor = updateLabelFor(*label);
3128+
3129+
if (!updatedLabelFor) {
31193130
auto labeledObjects = object->labelForObjects();
31203131
for (auto& labeledObject : labeledObjects) {
31213132
updateLabeledBy(RefPtr { labeledObject->element() }.get());
@@ -3126,10 +3137,16 @@ void AXObjectCache::handleLabelChanged(AccessibilityObject* object)
31263137
postNotification(object, protectedDocument().get(), AXNotification::LabelChanged);
31273138
}
31283139

3129-
void AXObjectCache::updateLabelFor(HTMLLabelElement& label)
3140+
bool AXObjectCache::updateLabelFor(HTMLLabelElement& label)
31303141
{
3142+
if (RefPtr control = Accessibility::controlForLabelElement(label)) {
3143+
if (hasAnyARIALabelling(*control))
3144+
return false;
3145+
}
3146+
31313147
removeRelation(label, AXRelation::LabelFor);
31323148
addLabelForRelation(label);
3149+
return true;
31333150
}
31343151

31353152
void AXObjectCache::updateLabeledBy(Element* element)
@@ -5341,9 +5358,7 @@ bool AXObjectCache::addRelation(Element& origin, Element& target, AXRelation rel
53415358

53425359
if (relation == AXRelation::LabelFor) {
53435360
// Add a LabelFor relation if the target doesn't have an ARIA label which should take precedence.
5344-
if (target.hasAttributeWithoutSynchronization(aria_labelAttr)
5345-
|| target.hasAttributeWithoutSynchronization(aria_labelledbyAttr)
5346-
|| target.hasAttributeWithoutSynchronization(aria_labeledbyAttr))
5361+
if (hasAnyARIALabelling(target))
53475362
return false;
53485363
}
53495364

Source/WebCore/accessibility/AXObjectCache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ class AXObjectCache final : public CanMakeWeakPtr<AXObjectCache>, public CanMake
728728
bool removeRelation(Element&, AXRelation);
729729
void removeAllRelations(AXID);
730730
void removeRelationByID(AXID originID, AXID targetID, AXRelation);
731-
void updateLabelFor(HTMLLabelElement&);
731+
bool updateLabelFor(HTMLLabelElement&);
732732
void updateLabeledBy(Element*);
733733
void updateRelationsIfNeeded();
734734
void updateRelationsForTree(ContainerNode&);

0 commit comments

Comments
 (0)