Skip to content
Permalink
Browse files
AX: Update the isolated tree in response to dynamic changes to aria-l…
…ive, aria-relevant, and aria-atomic

https://bugs.webkit.org/show_bug.cgi?id=241444

Reviewed by Chris Fleizach.

With this patch, we now update the isolated tree in response to dynamic
aria-live, aria-relevant, and aria-atomic changes.

This patch also makes liveRegionStatus, liveRegionRelevant, and
liveRegionAtomic work on node-only objects (like those with display:contents).

* LayoutTests/accessibility/aria-busy-updates-after-dynamic-change-expected.txt: Removed.
* LayoutTests/accessibility/aria-busy-updates-after-dynamic-change.html: Removed.
* LayoutTests/accessibility/live-region-attributes-update-after-dynamic-change-expected.txt: Added.
* LayoutTests/accessibility/live-region-attributes-update-after-dynamic-change.html: Added.
* LayoutTests/platform/glib/TestExpectations:
* LayoutTests/platform/ios/TestExpectations:
* LayoutTests/platform/win/TestExpectations:
* Source/WebCore/accessibility/AXLogger.cpp:
(WebCore::operator<<):
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::handleAttributeChange):
(WebCore::AXObjectCache::updateIsolatedTree):
* Source/WebCore/accessibility/AXObjectCache.h:
* Source/WebCore/accessibility/AccessibilityNodeObject.cpp:
(WebCore::AccessibilityNodeObject::liveRegionStatus const):
(WebCore::AccessibilityNodeObject::liveRegionRelevant const):
(WebCore::AccessibilityNodeObject::liveRegionAtomic const):
* Source/WebCore/accessibility/AccessibilityNodeObject.h:
* Source/WebCore/accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::liveRegionStatus const): Deleted.
(WebCore::AccessibilityRenderObject::liveRegionRelevant const): Deleted.
(WebCore::AccessibilityRenderObject::liveRegionAtomic const): Deleted.
* Source/WebCore/accessibility/AccessibilityRenderObject.h:
* Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp:
(WebCore::AXObjectCache::postPlatformNotification):
* Tools/DumpRenderTree/AccessibilityUIElement.cpp:
(getLiveRegionRelevantCallback):
(getLiveRegionStatusCallback):
(getIsAtomicLiveRegionCallback):
(AccessibilityUIElement::getJSClass):
* Tools/DumpRenderTree/AccessibilityUIElement.h:
* Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm:
(AccessibilityUIElement::liveRegionRelevant const):
(AccessibilityUIElement::liveRegionStatus const):
(AccessibilityUIElement::isAtomicLiveRegion const):
* Tools/DumpRenderTree/win/AccessibilityUIElementWin.cpp
(AccessibilityUIElement::liveRegionRelevant const):
(AccessibilityUIElement::liveRegionStatus const):
(AccessibilityUIElement::isAtomicLiveRegion const):
* Tools/DumpRenderTree/mac/AccessibilityUIElementMac.mm:
(AccessibilityUIElement::liveRegionRelevant const):
(AccessibilityUIElement::liveRegionStatus const):
(AccessibilityUIElement::isAtomicLiveRegion const):
* Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
* Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
* Tools/WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm:
(WTR::AccessibilityUIElement::liveRegionRelevant const):
(WTR::AccessibilityUIElement::liveRegionStatus const):
(WTR::AccessibilityUIElement::isAtomicLiveRegion const):
* Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
(WTR::AccessibilityUIElement::liveRegionStatus const):
(WTR::AccessibilityUIElement::liveRegionRelevant const):
(WTR::AccessibilityUIElement::isAtomicLiveRegion const):
* Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp
* Tools/WebKitTestRunner/InjectedBundle/win/AccessibilityUIElementWin.cpp

Canonical link: https://commits.webkit.org/251452@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@295446 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
twilco committed Jun 10, 2022
1 parent df83755 commit 56763037f7d6941d40b5b33abb3496e04720576e
Show file tree
Hide file tree
Showing 26 changed files with 349 additions and 125 deletions.

This file was deleted.

This file was deleted.

@@ -0,0 +1,48 @@
This test ensures that an element's busy state updates aria-busy changes.

Verifying initial states:

#clock is atomic: true
#clock-display-contents is atomic: true

#clock is busy: false
#clock-display-contents is busy: false

#clock relevant: additions text
#clock-display-contents relevant: additions text

#clock live region status: polite
#clock-display-contents live region status: polite

Setting aria-busy to true.

#clock is busy: true
#clock-display-contents is busy: true

Setting aria-busy to false.

#clock is busy: false
#clock-display-contents is busy: false

Setting aria-atomic to false.

#clock is atomic: false
#clock-display-contents is atomic: false

Setting aria-relevant to removals.

#clock relevant: removals
#clock-display-contents relevant: removals

Setting aria-live to assertive.

#clock live region status: assertive
#clock-display-contents live region status: assertive


PASS successfullyParsed is true

TEST COMPLETE
2:30pm
4:30pm

@@ -0,0 +1,85 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test.js"></script>
<script src="../resources/accessibility-helper.js"></script>
</head>
<body>

<div id="clock" role="timer" aria-live="polite" aria-atomic="true" aria-relevant="additions text">2:30pm</div>
<div id="clock-display-contents" style="display: contents" role="timer" aria-live="polite" aria-atomic="true" aria-relevant="additions text">4:30pm</div>

<script>
var testOutput = "This test ensures that an element's busy state updates aria-busy changes.\n\n";

var axClock, axClockWithDisplayContents;
function verifyIsBusy() {
testOutput += `#clock is busy: ${axClock.isBusy}\n`;
testOutput += `#clock-display-contents is busy: ${axClockWithDisplayContents.isBusy}\n\n`;
}
function verifyIsAtomic() {
testOutput += `#clock is atomic: ${axClock.isAtomicLiveRegion}\n`;
testOutput += `#clock-display-contents is atomic: ${axClockWithDisplayContents.isAtomicLiveRegion}\n\n`;
}
function verifyLiveRegionRelevant() {
testOutput += `#clock relevant: ${axClock.liveRegionRelevant}\n`;
testOutput += `#clock-display-contents relevant: ${axClockWithDisplayContents.liveRegionRelevant}\n\n`;
}
function verifyLiveRegionStatus() {
testOutput += `#clock live region status: ${axClock.liveRegionStatus}\n`;
testOutput += `#clock-display-contents live region status: ${axClockWithDisplayContents.liveRegionStatus}\n\n`;
}

if (window.accessibilityController) {
window.jsTestIsAsync = true;
axClock = accessibilityController.accessibleElementById("clock");
axClockWithDisplayContents = accessibilityController.accessibleElementById("clock-display-contents");

testOutput += "Verifying initial states:\n\n";
verifyIsAtomic();
verifyIsBusy();
verifyLiveRegionRelevant();
verifyLiveRegionStatus();

const clock = document.getElementById("clock");
const clockWithDisplayContents = document.getElementById("clock-display-contents");

testOutput += "Setting aria-busy to true.\n\n";
clock.ariaBusy = "true";
clockWithDisplayContents.ariaBusy = "true";
setTimeout(async function() {
await waitFor(() => axClock.isBusy && axClockWithDisplayContents.isBusy);
verifyIsBusy();

testOutput += "Setting aria-busy to false.\n\n";
clock.ariaBusy = "false";
clockWithDisplayContents.ariaBusy = "false";
await waitFor(() => !axClock.isBusy && !axClockWithDisplayContents.isBusy);
verifyIsBusy();

testOutput += "Setting aria-atomic to false.\n\n";
clock.ariaAtomic = "false";
clockWithDisplayContents.ariaAtomic = "false";
await waitFor(() => !axClock.isAtomicLiveRegion && !axClockWithDisplayContents.isAtomicLiveRegion);
verifyIsAtomic();

testOutput += "Setting aria-relevant to removals.\n\n";
clock.ariaRelevant = "removals";
clockWithDisplayContents.ariaRelevant = "removals";
await waitFor(() => axClock.liveRegionRelevant.includes("removals") && axClockWithDisplayContents.liveRegionRelevant.includes("removals"));
verifyLiveRegionRelevant();

testOutput += "Setting aria-live to assertive.\n\n";
clock.ariaLive = "assertive";
clockWithDisplayContents.ariaLive = "assertive";
await waitFor(() => axClock.liveRegionStatus.includes("assertive") && axClockWithDisplayContents.liveRegionStatus.includes("assertive"));
verifyLiveRegionStatus();

debug(testOutput);
finishJSTest();
}, 0);
}
</script>
</body>
</html>

@@ -344,8 +344,8 @@ accessibility/ancestor-computation.html [ Skip ]
# Need to implement AccessibilityUIElement::domIdentifier() for this test to pass after webkit.org/b/234198.
accessibility/focusable-div.html [ Skip ]

# Missing AccessibilityUIElement::isBusy implementation.
accessibility/aria-busy-updates-after-dynamic-change.html [ Skip ]
# Missing AccessibilityUIElement::{isBusy, isAtomicLiveRegion, liveRegionStatus, liveRegionRelevant} implementations.
accessibility/live-region-attributes-update-after-dynamic-change.html [ Skip ]

# Timing out since it was added in https://bugs.webkit.org/show_bug.cgi?id=239434.
accessibility/text-updates-after-dynamic-change.html [ Skip ]
@@ -2115,7 +2115,6 @@ webkit.org/b/148806 imported/w3c/web-platform-tests/css/css-multicol/multicol-sp
fast/dom/linkify-phone-numbers.html [ Pass ]

accessibility/table-exposure-updates-dynamically.html [ Pass ]
accessibility/aria-busy-updates-after-dynamic-change.html [ Pass ]
accessibility/aria-describedby-on-input.html [ Pass ]
accessibility/aria-hidden-display-contents-element.html [ Pass ]
accessibility/aria-multiline.html [ Pass ]
@@ -2126,6 +2125,7 @@ accessibility/display-contents-element-roles.html [ Pass ]
accessibility/element-haspopup.html [ Pass ]
accessibility/heading-level.html [ Pass ]
accessibility/list-with-dynamically-changing-content.html [ Pass ]
accessibility/live-region-attributes-update-after-dynamic-change.html [ Pass ]
accessibility/node-only-inert-object.html [ Pass ]
accessibility/node-only-object-element-rect.html [ Pass ]
accessibility/text-updates-after-dynamic-change.html [ Pass ]
@@ -501,8 +501,8 @@ accessibility/ignore-modals-without-any-content.html [ Skip ]
accessibility/aria-readonly-updates-after-dynamic-change.html [ Skip ]
# Missing AccessibilityUIElement::isRequired implementation.
accessibility/aria-required-updates-after-dynamic-change.html [ Skip ]
# Missing AccessibilityUIElement::isBusy implementation.
accessibility/aria-busy-updates-after-dynamic-change.html [ Skip ]
# Missing AccessibilityUIElement::{isBusy, isAtomicLiveRegion, liveRegionStatus, liveRegionRelevant} implementations.
accessibility/live-region-attributes-update-after-dynamic-change.html [ Skip ]

# TODO Conic gradients
http/wpt/css/css-images-4/conic-gradient-parsing.html [ Skip ]
@@ -406,6 +406,9 @@ TextStream& operator<<(TextStream& stream, AXObjectCache::AXNotification notific
case AXObjectCache::AXNotification::AXImageOverlayChanged:
stream << "AXImageOverlayChanged";
break;
case AXObjectCache::AXNotification::AXIsAtomicChanged:
stream << "AXIsAtomicChanged";
break;
case AXObjectCache::AXNotification::AXLanguageChanged:
stream << "AXLanguageChanged";
break;
@@ -460,6 +463,12 @@ TextStream& operator<<(TextStream& stream, AXObjectCache::AXNotification notific
case AXObjectCache::AXNotification::AXLiveRegionChanged:
stream << "AXLiveRegionChanged";
break;
case AXObjectCache::AXNotification::AXLiveRegionRelevantChanged:
stream << "AXLiveRegionRelevantChanged";
break;
case AXObjectCache::AXNotification::AXLiveRegionStatusChanged:
stream << "AXLiveRegionStatusChanged";
break;
case AXObjectCache::AXNotification::AXMenuListItemSelected:
stream << "AXMenuListItemSelected";
break;
@@ -1940,6 +1940,8 @@ void AXObjectCache::handleAttributeChange(const QualifiedName& attrName, Element

if (attrName == aria_activedescendantAttr)
handleActiveDescendantChanged(*element);
else if (attrName == aria_atomicAttr)
postNotification(element, AXIsAtomicChanged);
else if (attrName == aria_busyAttr)
postNotification(element, AXObjectCache::AXElementBusyChanged);
else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr)
@@ -1954,6 +1956,8 @@ void AXObjectCache::handleAttributeChange(const QualifiedName& attrName, Element
postNotification(element, AXGrabbedStateChanged);
else if (attrName == aria_levelAttr)
postNotification(element, AXLevelChanged);
else if (attrName == aria_liveAttr)
postNotification(element, AXLiveRegionStatusChanged);
else if (attrName == aria_valuemaxAttr)
postNotification(element, AXMaximumValueChanged);
else if (attrName == aria_valueminAttr)
@@ -1969,6 +1973,8 @@ void AXObjectCache::handleAttributeChange(const QualifiedName& attrName, Element
postNotification(element, AXMultiSelectableStateChanged);
else if (attrName == aria_posinsetAttr)
postNotification(element, AXPositionInSetChanged);
else if (attrName == aria_relevantAttr)
postNotification(element, AXLiveRegionRelevantChanged);
else if (attrName == aria_selectedAttr)
selectedStateChanged(element);
else if (attrName == aria_setsizeAttr)
@@ -3560,7 +3566,10 @@ void AXObjectCache::updateIsolatedTree(const Vector<std::pair<RefPtr<AXCoreObjec
case AXGrabbedStateChanged:
case AXHasPopupChanged:
case AXInvalidStatusChanged:
case AXIsAtomicChanged:
case AXLevelChanged:
case AXLiveRegionStatusChanged:
case AXLiveRegionRelevantChanged:
case AXMenuListValueChanged:
case AXMultiSelectableStateChanged:
case AXPressedStateChanged:
@@ -288,6 +288,7 @@ class AXObjectCache {
AXHasPopupChanged,
AXIdAttributeChanged,
AXImageOverlayChanged,
AXIsAtomicChanged,
AXLanguageChanged,
AXLayoutComplete,
AXLevelChanged,
@@ -303,6 +304,8 @@ class AXObjectCache {
AXScrolledToAnchor,
AXLiveRegionCreated,
AXLiveRegionChanged,
AXLiveRegionRelevantChanged,
AXLiveRegionStatusChanged,
AXMaximumValueChanged,
AXMenuListItemSelected,
AXMenuListValueChanged,
@@ -1304,6 +1304,43 @@ bool AccessibilityNodeObject::elementAttributeValue(const QualifiedName& attribu
return equalLettersIgnoringASCIICase(getAttribute(attributeName), "true"_s);
}

const String AccessibilityNodeObject::liveRegionStatus() const
{
const auto& liveRegionStatus = getAttribute(aria_liveAttr);
if (liveRegionStatus.isEmpty())
return defaultLiveRegionStatusForRole(roleValue());

return liveRegionStatus;
}

const String AccessibilityNodeObject::liveRegionRelevant() const
{
const auto& relevant = getAttribute(aria_relevantAttr);
// Default aria-relevant = "additions text".
if (relevant.isEmpty())
return "additions text"_s;

return relevant;
}

bool AccessibilityNodeObject::liveRegionAtomic() const
{
const auto& atomic = getAttribute(aria_atomicAttr);
if (equalLettersIgnoringASCIICase(atomic, "true"_s))
return true;
if (equalLettersIgnoringASCIICase(atomic, "false"_s))
return false;

// WAI-ARIA "alert" and "status" roles have an implicit aria-atomic value of true.
switch (roleValue()) {
case AccessibilityRole::ApplicationAlert:
case AccessibilityRole::ApplicationStatus:
return true;
default:
return false;
}
}

bool AccessibilityNodeObject::isGenericFocusableElement() const
{
if (!canSetFocusAttribute())
@@ -173,6 +173,10 @@ class AccessibilityNodeObject : public AccessibilityObject {

bool elementAttributeValue(const QualifiedName&) const;

const String liveRegionStatus() const override;
const String liveRegionRelevant() const override;
bool liveRegionAtomic() const override;

bool isLabelable() const;
AccessibilityObject* correspondingControlForLabelElement() const override;
AccessibilityObject* correspondingLabelForControlElement() const override;

0 comments on commit 5676303

Please sign in to comment.