Bug 1915262 - Fire queued live region event from content in MacOS. r=morgan

Introduce a gecko live region changed event and fire it from within content.
This way it gets coalesced in the case of many insertions/deletions.

Also, rely on text insert/delete instead of reorder because there can be cases
where the text in a leaf changes as opposed to a tree mutation.
We get text insert/delete on mutations too, so that should cover it.

Differential Revision: https://phabricator.services.mozilla.com/D224388
This commit is contained in:
Eitan Isaacson
2024-10-09 05:36:21 +00:00
parent 2162985d40
commit 461761b786
8 changed files with 79 additions and 44 deletions

View File

@@ -54,6 +54,8 @@ class AccessibleWrap : public LocalAccessible {
virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
static bool IsLiveRegion(nsIContent* aContent);
protected:
friend class xpcAccessibleMacInterface;

View File

@@ -35,34 +35,11 @@ AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
: LocalAccessible(aContent, aDoc),
mNativeObject(nil),
mNativeInited(false) {
if (aContent && aContent->IsElement() && aDoc) {
if (aContent && aDoc && IsLiveRegion(aContent)) {
// Check if this accessible is a live region and queue it
// it for dispatching an event after it has been inserted.
DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
static const dom::Element::AttrValuesArray sLiveRegionValues[] = {
nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr};
int32_t attrValue = nsAccUtils::FindARIAAttrValueIn(
aContent->AsElement(), nsGkAtoms::aria_live, sLiveRegionValues,
eIgnoreCase);
if (attrValue == 0) {
// aria-live is "off", do nothing.
} else if (attrValue > 0) {
// aria-live attribute is polite or assertive. It's live!
doc->QueueNewLiveRegion(this);
} else if (const nsRoleMapEntry* roleMap =
aria::GetRoleMap(aContent->AsElement())) {
// aria role defines it as a live region. It's live!
if (roleMap->liveAttRule == ePoliteLiveAttr ||
roleMap->liveAttRule == eAssertiveLiveAttr) {
doc->QueueNewLiveRegion(this);
}
} else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
aContent, nsGkAtoms::aria_live)) {
// HTML element defines it as a live region. It's live!
if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) {
doc->QueueNewLiveRegion(this);
}
}
doc->QueueNewLiveRegion(this);
}
}
@@ -167,11 +144,58 @@ nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
doc->ProcessNewLiveRegions();
}
if ((eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED ||
eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
eventType == nsIAccessibleEvent::EVENT_NAME_CHANGE) &&
!aEvent->FromUserInput()) {
for (LocalAccessible* container = aEvent->GetAccessible(); container;
container = container->LocalParent()) {
if (container->HasOwnContent() && IsLiveRegion(container->GetContent())) {
// We rely on EventQueue::CoalesceEvents to remove duplicates
Document()->FireDelayedEvent(
nsIAccessibleEvent::EVENT_LIVE_REGION_CHANGED, container);
}
}
}
return NS_OK;
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}
bool AccessibleWrap::IsLiveRegion(nsIContent* aContent) {
if (!aContent->IsElement()) {
return false;
}
static const dom::Element::AttrValuesArray sLiveRegionValues[] = {
nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr};
int32_t attrValue = nsAccUtils::FindARIAAttrValueIn(
aContent->AsElement(), nsGkAtoms::aria_live, sLiveRegionValues,
eIgnoreCase);
if (attrValue == 0) {
// aria-live is "off", do nothing.
} else if (attrValue > 0) {
// aria-live attribute is polite or assertive. It's live!
return true;
} else if (const nsRoleMapEntry* roleMap =
aria::GetRoleMap(aContent->AsElement())) {
// aria role defines it as a live region. It's live!
if (roleMap->liveAttRule == ePoliteLiveAttr ||
roleMap->liveAttRule == eAssertiveLiveAttr) {
return true;
}
} else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
aContent, nsGkAtoms::aria_live)) {
// HTML element defines it as a live region. It's live!
if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// AccessibleWrap protected

View File

@@ -99,6 +99,7 @@ void PlatformEvent(Accessible* aTarget, uint32_t aEventType) {
aEventType != nsIAccessibleEvent::EVENT_REORDER &&
aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED &&
aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED &&
aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_CHANGED &&
aEventType != nsIAccessibleEvent::EVENT_NAME_CHANGE &&
aEventType != nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED) {
return;

View File

@@ -43,7 +43,6 @@ using namespace mozilla::a11y;
@interface mozAccessible ()
- (BOOL)providesLabelNotTitle;
- (void)maybePostLiveRegionChanged;
- (void)maybePostA11yUtilNotification;
@end
@@ -869,17 +868,6 @@ struct RoleDescrComparator {
return NO;
}
- (void)maybePostLiveRegionChanged {
id<MOXAccessible> liveRegion =
[self moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
return [moxAcc moxIsLiveRegion];
}];
if (liveRegion) {
[liveRegion moxPostNotification:@"AXLiveRegionChanged"];
}
}
- (void)maybePostA11yUtilNotification {
MOZ_ASSERT(mGeckoAccessible);
// Sometimes we use a special live region to make announcements to the user.
@@ -1004,16 +992,15 @@ struct RoleDescrComparator {
case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED:
mIsLiveRegion = false;
break;
case nsIAccessibleEvent::EVENT_REORDER:
[self maybePostLiveRegionChanged];
break;
case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
case nsIAccessibleEvent::EVENT_NAME_CHANGE:
if (![self providesLabelNotTitle]) {
[self moxPostNotification:NSAccessibilityTitleChangedNotification];
}
[self maybePostLiveRegionChanged];
break;
}
case nsIAccessibleEvent::EVENT_LIVE_REGION_CHANGED:
MOZ_ASSERT(mIsLiveRegion);
[self moxPostNotification:@"AXLiveRegionChanged"];
break;
}
}