Skip to content

Commit

Permalink
AX: Accessibility tree not updated in response to aria-owns removal
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=261645
rdar://problem/115607658

Reviewed by Chris Fleizach.

This patch also fixes a bug where AXPropertyName::SupportsARIAOwns was
not updated in response to dynamic aria-owns changes (additions or
removals).

* LayoutTests/accessibility/aria-owns-expected.txt:
* LayoutTests/accessibility/aria-owns.html:
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::handleAttributeChange):
(WebCore::AXObjectCache::updateIsolatedTree const):
* Source/WebCore/accessibility/AXObjectCache.h:
* Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp:
(WebCore::AXIsolatedTree::updateNodeProperties):

Canonical link: https://commits.webkit.org/268170@main
  • Loading branch information
twilco committed Sep 20, 2023
1 parent 012dba2 commit 95289b1
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 26 deletions.
24 changes: 15 additions & 9 deletions LayoutTests/accessibility/aria-owns-expected.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
Item 1
Item 2
Item 3
BUTTON
RADIO BUTTON
This tests that aria-owns correctly exposes AXOwns and correctly returns the right elements

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS: group.isAttributeSupported('AXOwns') === true
PASS: group.ariaOwnsElementAtIndex(0).role === 'AXRole: AXButton'
PASS: group.ariaOwnsElementAtIndex(0).title === 'AXTitle: BUTTON'
PASS: group.ariaOwnsElementAtIndex(1).role === 'AXRole: AXRadioButton'
PASS: group.ariaOwnsElementAtIndex(1).title === 'AXTitle: RADIO BUTTON'
PASS: group.childrenCount === 5
PASS: group.childAtIndex(4).title === 'AXTitle: RADIO BUTTON'
document.getElementById('group').removeAttribute('aria-owns')
PASS: group.childrenCount === 3
PASS: group.isAttributeSupported('AXOwns') === false
document.getElementById('group').setAttribute('aria-owns', 'extra')
PASS: group.childrenCount === 4
PASS: group.isAttributeSupported('AXOwns') === true
PASS: group.childAtIndex(3).title === 'AXTitle: BUTTON'

PASS successfullyParsed is true

TEST COMPLETE

Item 1
Item 2
Item 3
BUTTON
RADIO BUTTON
44 changes: 27 additions & 17 deletions LayoutTests/accessibility/aria-owns.html
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test.js"></script>
<script src="../resources/accessibility-helper.js"></script>
<script src="../resources/js-test.js"></script>
</head>
<body id="body">
<body>

<div tabindex=0 id="group" role="group" aria-owns="extra extra2">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>

<div id="extra" role="button">BUTTON</div>
<div id="extra2" role="radio">RADIO BUTTON</div>

<p id="description"></p>
<div id="console"></div>

<script>

description("This tests that aria-owns correctly exposes AXOwns and correctly returns the right elements");
var output = "This tests that aria-owns correctly exposes AXOwns and correctly returns the right elements\n\n";

if (window.accessibilityController) {
window.jsTestIsAsync = true;

var output = "";
var group;
document.getElementById("group").focus();
setTimeout(function() {
group = accessibilityController.focusedElement;
setTimeout(async () => {
group = accessibilityController.accessibleElementById("group");
if (accessibilityController.platformName === "mac")
output += expect("group.isAttributeSupported('AXOwns')", "true");
output += expect("group.ariaOwnsElementAtIndex(0).role", "'AXRole: AXButton'");
output += expect("group.ariaOwnsElementAtIndex(0).title", "'AXTitle: BUTTON'");
output += expect("group.ariaOwnsElementAtIndex(1).role", "'AXRole: AXRadioButton'");
output += expect("group.ariaOwnsElementAtIndex(1).title", "'AXTitle: RADIO BUTTON'");
output += expect("group.childrenCount", "5");
output += expect("group.childAtIndex(4).title", "'AXTitle: RADIO BUTTON'");

// Remove aria-owns and confirm children count has updated.
output += evalAndReturn("document.getElementById('group').removeAttribute('aria-owns')");
output += await expectAsync("group.childrenCount", "3");
if (accessibilityController.platformName === "mac")
output += await expectAsync("group.isAttributeSupported('AXOwns')", "false");

// Add one ARIA child back.
output += evalAndReturn("document.getElementById('group').setAttribute('aria-owns', 'extra')");
output += await expectAsync("group.childrenCount", "4");
if (accessibilityController.platformName === "mac")
output += await expectAsync("group.isAttributeSupported('AXOwns')", "true");
output += expect("group.childAtIndex(3).title", "'AXTitle: BUTTON'");

debug(output);
finishJSTest();
}, 0);
});
}

</script>

</body>
</html>
22 changes: 22 additions & 0 deletions LayoutTests/platform/glib/accessibility/aria-owns-expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
This tests that aria-owns correctly exposes AXOwns and correctly returns the right elements

PASS: group.ariaOwnsElementAtIndex(0).role === 'AXRole: AXButton'
PASS: group.ariaOwnsElementAtIndex(0).title === 'AXTitle: BUTTON'
PASS: group.ariaOwnsElementAtIndex(1).role === 'AXRole: AXRadioButton'
PASS: group.ariaOwnsElementAtIndex(1).title === 'AXTitle: RADIO BUTTON'
PASS: group.childrenCount === 5
PASS: group.childAtIndex(4).title === 'AXTitle: RADIO BUTTON'
document.getElementById('group').removeAttribute('aria-owns')
PASS: group.childrenCount === 3
document.getElementById('group').setAttribute('aria-owns', 'extra')
PASS: group.childrenCount === 4
PASS: group.childAtIndex(3).title === 'AXTitle: BUTTON'

PASS successfullyParsed is true

TEST COMPLETE
Item 1
Item 2
Item 3
BUTTON
RADIO BUTTON
24 changes: 24 additions & 0 deletions Source/WebCore/accessibility/AXObjectCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2555,6 +2555,12 @@ void AXObjectCache::handleAttributeChange(Element* element, const QualifiedName&
recomputeParentTableProperties(element, { TableProperty::CellSlots, TableProperty::Exposed });
} else if (attrName == aria_sortAttr)
postNotification(element, AXObjectCache::AXSortDirectionChanged);
else if (attrName == aria_ownsAttr) {
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
if (oldValue.isEmpty() || newValue.isEmpty())
updateIsolatedTree(get(element), AXPropertyName::SupportsARIAOwns);
#endif
}
}

void AXObjectCache::labelChanged(Element* element)
Expand Down Expand Up @@ -4209,6 +4215,21 @@ void AXObjectCache::updateIsolatedTree(const Vector<std::pair<RefPtr<Accessibili
}
}

void AXObjectCache::updateIsolatedTree(AccessibilityObject* axObject, AXPropertyName property) const
{
if (axObject)
updateIsolatedTree(*axObject, property);
}

void AXObjectCache::updateIsolatedTree(AccessibilityObject& axObject, AXPropertyName property) const
{
RefPtr tree = AXIsolatedTree::treeForPageID(*m_pageID);
if (!tree)
return;

tree->updateNodeProperty(axObject, property);
}

void AXObjectCache::onPaint(const RenderObject& renderer, IntRect&& paintRect) const
{
if (!m_pageID)
Expand Down Expand Up @@ -4567,6 +4588,9 @@ void AXObjectCache::removeRelations(Element& origin, AXRelationType relationType

for (AXID targetID : targetIDs)
removeRelationByID(targetID, object->objectID(), symmetric);

if (!targetIDs.isEmpty() && relationType == AXRelationType::OwnerFor)
childrenChanged(object);
}

void AXObjectCache::removeRelationByID(AXID originID, AXID targetID, AXRelationType relationType)
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/accessibility/AXObjectCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@ class AXObjectCache : public CanMakeWeakPtr<AXObjectCache>, public CanMakeChecke
void updateIsolatedTree(AccessibilityObject&, AXNotification);
void updateIsolatedTree(AccessibilityObject*, AXNotification);
void updateIsolatedTree(const Vector<std::pair<RefPtr<AccessibilityObject>, AXNotification>>&);
void updateIsolatedTree(AccessibilityObject*, AXPropertyName) const;
void updateIsolatedTree(AccessibilityObject&, AXPropertyName) const;
#endif

protected:
Expand Down
3 changes: 3 additions & 0 deletions Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,9 @@ void AXIsolatedTree::updateNodeProperties(AXCoreObject& axObject, const Vector<A
case AXPropertyName::SelectedChildren:
propertyMap.set(AXPropertyName::SelectedChildren, axIDs(axObject.selectedChildren()));
break;
case AXPropertyName::SupportsARIAOwns:
propertyMap.set(AXPropertyName::SupportsARIAOwns, axObject.supportsARIAOwns());
break;
case AXPropertyName::SupportsPosInSet:
propertyMap.set(AXPropertyName::SupportsPosInSet, axObject.supportsPosInSet());
break;
Expand Down

0 comments on commit 95289b1

Please sign in to comment.