зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1479042 - Handle text insertion and name change events as live regions and announce. r=yzen
Differential Revision: https://phabricator.services.mozilla.com/D21612 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
6d8e53ccd1
Коммит
c35dbb6950
|
@ -13,8 +13,10 @@
|
|||
#include "SessionAccessibility.h"
|
||||
#include "nsAccessibilityService.h"
|
||||
#include "nsPersistentProperties.h"
|
||||
#include "nsIAccessibleAnnouncementEvent.h"
|
||||
#include "nsIStringBundle.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "nsTextEquivUtils.h"
|
||||
|
||||
#include "mozilla/a11y/PDocAccessibleChild.h"
|
||||
|
||||
|
@ -84,6 +86,8 @@ nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
|
|||
nsresult rv = Accessible::HandleAccEvent(aEvent);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
accessible->HandleLiveRegionEvent(aEvent);
|
||||
|
||||
if (IPCAccessibilityActive()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -619,3 +623,77 @@ mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(
|
|||
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
void AccessibleWrap::GetTextEquiv(nsString& aText) {
|
||||
if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule)) {
|
||||
// This is an accessible that normally doesn't get its name from its
|
||||
// subtree, so we collect the text equivalent explicitly.
|
||||
nsTextEquivUtils::GetTextEquivFromSubtree(this, aText);
|
||||
} else {
|
||||
Name(aText);
|
||||
}
|
||||
}
|
||||
|
||||
bool AccessibleWrap::HandleLiveRegionEvent(AccEvent* aEvent) {
|
||||
auto eventType = aEvent->GetEventType();
|
||||
if (eventType != nsIAccessibleEvent::EVENT_TEXT_INSERTED &&
|
||||
eventType != nsIAccessibleEvent::EVENT_NAME_CHANGE) {
|
||||
// XXX: Right now only announce text inserted events. aria-relevant=removals
|
||||
// is potentially on the chopping block[1]. We also don't support editable
|
||||
// text because we currently can't descern the source of the change[2].
|
||||
// 1. https://github.com/w3c/aria/issues/712
|
||||
// 2. https://bugzilla.mozilla.org/show_bug.cgi?id=1531189
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aEvent->IsFromUserInput()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
|
||||
nsString live;
|
||||
nsresult rv =
|
||||
attributes->GetStringProperty(NS_LITERAL_CSTRING("container-live"), live);
|
||||
if (!NS_SUCCEEDED(rv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t priority = live.EqualsIgnoreCase("assertive")
|
||||
? nsIAccessibleAnnouncementEvent::ASSERTIVE
|
||||
: nsIAccessibleAnnouncementEvent::POLITE;
|
||||
|
||||
nsString atomic;
|
||||
rv = attributes->GetStringProperty(NS_LITERAL_CSTRING("container-atomic"),
|
||||
atomic);
|
||||
|
||||
Accessible* announcementTarget = this;
|
||||
nsAutoString announcement;
|
||||
if (atomic.EqualsIgnoreCase("true")) {
|
||||
Accessible* atomicAncestor = nullptr;
|
||||
for (Accessible* parent = announcementTarget; parent;
|
||||
parent = parent->Parent()) {
|
||||
Element* element = parent->Elm();
|
||||
if (element &&
|
||||
element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic,
|
||||
nsGkAtoms::_true, eCaseMatters)) {
|
||||
atomicAncestor = parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (atomicAncestor) {
|
||||
announcementTarget = atomicAncestor;
|
||||
static_cast<AccessibleWrap*>(atomicAncestor)->GetTextEquiv(announcement);
|
||||
}
|
||||
} else {
|
||||
GetTextEquiv(announcement);
|
||||
}
|
||||
|
||||
announcement.CompressWhitespace();
|
||||
if (announcement.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
announcementTarget->Announce(announcement, priority);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -79,6 +79,10 @@ class AccessibleWrap : public Accessible {
|
|||
|
||||
virtual role WrapperRole() { return Role(); }
|
||||
|
||||
void GetTextEquiv(nsString& aText);
|
||||
|
||||
bool HandleLiveRegionEvent(AccEvent* aEvent);
|
||||
|
||||
static void GetRoleDescription(role aRole,
|
||||
nsIPersistentProperties* aAttributes,
|
||||
nsAString& aGeckoRole,
|
||||
|
|
|
@ -103,6 +103,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
fun onTextTraversal(event: AccessibilityEvent) { }
|
||||
fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
fun onWinStateChanged(event: AccessibilityEvent) { }
|
||||
fun onAnnouncement(event: AccessibilityEvent) { }
|
||||
}
|
||||
|
||||
@Before fun setup() {
|
||||
|
@ -134,6 +135,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
|
||||
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> newDelegate.onWinContentChanged(event)
|
||||
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
|
||||
AccessibilityEvent.TYPE_ANNOUNCEMENT -> newDelegate.onAnnouncement(event)
|
||||
else -> {}
|
||||
}
|
||||
return false
|
||||
|
@ -591,6 +593,9 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
createNodeInfo(rootNode.getChildId(0)).childCount, equalTo(1))
|
||||
mainSession.evaluateJS("$('#to_show').style.display = 'none';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 0)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) { }
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
|
@ -599,6 +604,105 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
createNodeInfo(rootNode.getChildId(0)).childCount, equalTo(0))
|
||||
}
|
||||
|
||||
@Test fun testLiveRegion() {
|
||||
sessionRule.session.loadString(
|
||||
"<div id=\"to_change\"aria-live=\"polite\"></div>","text/html")
|
||||
waitForInitialFocus()
|
||||
|
||||
mainSession.evaluateJS("$('#to_change').textContent = 'Hello';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("Hello"))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
}
|
||||
|
||||
@Test fun testLiveRegionDescendant() {
|
||||
sessionRule.session.loadString(
|
||||
"<div aria-live='polite'><p id='to_show'>I will be shown</p></div>","text/html")
|
||||
waitForInitialFocus()
|
||||
|
||||
mainSession.evaluateJS("$('#to_show').style.display = 'none';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 0)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) { }
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
|
||||
mainSession.evaluateJS("$('#to_show').style.display = 'block';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("I will be shown"))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
}
|
||||
|
||||
@Test fun testLiveRegionAtomic() {
|
||||
sessionRule.session.loadString(
|
||||
"<div aria-live='polite' aria-atomic='true' id='container'>The time is <p>3pm</p></div>","text/html")
|
||||
waitForInitialFocus()
|
||||
|
||||
mainSession.evaluateJS("$('p').textContent = '4pm';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("The time is 4pm"))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
|
||||
mainSession.evaluateJS("$('#container').removeAttribute('aria-atomic'); $('p').textContent = '5pm';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("5pm"))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
}
|
||||
|
||||
@Test fun testLiveRegionImage() {
|
||||
sessionRule.session.loadString(
|
||||
"<div aria-live='polite' aria-atomic='true'>This picture is <img src='data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==' alt='happy'></div>","text/html")
|
||||
waitForInitialFocus()
|
||||
|
||||
mainSession.evaluateJS("console.log('eeejay', $('img').naturalWidth); $('img').alt = 'sad';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("This picture is sad"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Test fun testLiveRegionImageLabeledBy() {
|
||||
sessionRule.session.loadString(
|
||||
"<img src='data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==' aria-live='polite' aria-labelledby='l1'><span id='l1'>Hello</span><span id='l2'>Goodbye</span>","text/html")
|
||||
waitForInitialFocus()
|
||||
|
||||
mainSession.evaluateJS("$('img').setAttribute('aria-labelledby', 'l2');")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("Goodbye"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun screenContainsNode(nodeId: Int): Boolean {
|
||||
var node = createNodeInfo(nodeId)
|
||||
var nodeBounds = Rect()
|
||||
|
|
Загрузка…
Ссылка в новой задаче