Bug 493683 part 2: When an Accessible mutates, fire name change events on an Accessible labelled by it (or an ancestor thereof) via a relation. r=eeejay

HasNameDependent is now set on an Accessible (and thus its descendants) which has A LABEL_FOR relation.
When text mutations occur on such an Accessible, EventQueue::PushNameChange now queues a name change for the Accessible being labelled.

Differential Revision: https://phabricator.services.mozilla.com/D102677
This commit is contained in:
James Teh 2021-01-27 00:34:21 +00:00
Родитель 1af2138c00
Коммит 77fe9db976
4 изменённых файлов: 59 добавлений и 15 удалений

Просмотреть файл

@ -14,6 +14,7 @@
#ifdef A11Y_LOG
# include "Logging.h"
#endif
#include "Relation.h"
namespace mozilla {
namespace a11y {
@ -48,31 +49,41 @@ bool EventQueue::PushEvent(AccEvent* aEvent) {
}
bool EventQueue::PushNameChange(Accessible* aTarget) {
// Fire name change event on parent given that this event hasn't been
// coalesced, the parent's name was calculated from its subtree, and the
// subtree was changed.
// Fire name change event on parent or related Accessible being labelled given
// that this event hasn't been coalesced, the dependent's name was calculated
// from this subtree, and the subtree was changed.
bool pushed = false;
bool checkAncestor = true;
if (aTarget->HasNameDependent()) {
// Only continue traversing up the tree if it's possible that the parent
// accessible's name can depend on this accessible's name.
// Accessible's name (or an Accessible being labelled by this Accessible or
// an ancestor) can depend on this Accessible's name.
Accessible* parent = aTarget->Parent();
while (parent &&
nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) {
// Test possible name dependent parent.
if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
if (checkAncestor &&
nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
nsAutoString name;
ENameValueFlag nameFlag = parent->Name(name);
// If name is obtained from subtree, fire name change event.
if (nameFlag == eNameFromSubtree) {
RefPtr<AccEvent> nameChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
return PushEvent(nameChangeEvent);
pushed |= PushEvent(nameChangeEvent);
}
break;
checkAncestor = false;
}
Relation rel = parent->RelationByType(RelationType::LABEL_FOR);
while (Accessible* relTarget = rel.Next()) {
RefPtr<AccEvent> nameChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, relTarget);
pushed |= PushEvent(nameChangeEvent);
}
parent = parent->Parent();
}
}
return false;
return pushed;
}
////////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -114,9 +114,8 @@ class NotificationController final : public EventQueue,
}
/**
* Creates and adds a name change event into the queue for a container of
* the given accessible, if the accessible is a part of name computation of
* the container.
* Creates and adds a name change event into the queue for an Accessible which
* depends on the given Accessible for name computation, if any.
*/
void QueueNameChange(Accessible* aChangeTarget) {
if (PushNameChange(aChangeTarget)) {

Просмотреть файл

@ -2069,8 +2069,8 @@ void Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) {
mParent = aParent;
mIndexInParent = aIndexInParent;
// Note: this is currently only used for richlistitems and their children.
if (mParent->HasNameDependent() || mParent->IsXULListItem()) {
if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
RelationByType(RelationType::LABEL_FOR).Next()) {
mContextFlags |= eHasNameDependent;
} else {
mContextFlags &= ~eHasNameDependent;

Просмотреть файл

@ -19,6 +19,11 @@
src="../states.js"></script>
<script type="application/javascript">
let PromEvents = {};
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
PromEvents);
// //////////////////////////////////////////////////////////////////////////
// Invokers
@ -57,8 +62,15 @@
// gA11yEventDumpToConsole = true; // debuggin
var gQueue = null;
function doTests() {
async function doTests() {
gQueue = new eventQueue();
// Later tests use await.
let queueFinished = new Promise(resolve => {
gQueue.onFinish = function() {
resolve();
return DO_NOT_FINISH_TEST;
};
});
gQueue.push(new setAttr("tst1", "aria-label", "hi",
new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
@ -88,7 +100,26 @@
gQueue.push(new setAttr("tst4", "title", "title",
new invokerChecker(EVENT_NAME_CHANGE, "tst4")));
gQueue.invoke(); // Will call SimpleTest.finish();
gQueue.invoke();
await queueFinished;
// Tests beyond this point use await rather than eventQueue.
const labelledBy = getNode("labelledBy");
const label = getNode("label");
let nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy);
info("Changing text of aria-labelledby target");
label.textContent = "l2";
await nameChanged;
nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy);
info("Adding node to aria-labelledby target");
label.innerHTML = '<p id="labelChild">l3</p>';
await nameChanged;
nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy);
info("Changing text of aria-labelledby target's child");
getNode("labelChild").textContent = "l4";
await nameChanged;
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
@ -114,6 +145,9 @@
<img id="tst3">
<img id="tst4" src="../moz.png">
<div id="labelledBy" aria-labelledby="label"></div>
<div id="label">l1</div>
<div id="eventdump"></div>
</body>
</html>