mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
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:
parent
815152ffd4
commit
4d81a9a27a
@ -522,6 +522,7 @@ static const char kEventTypeNames[][40] = {
|
||||
"live region added", // EVENT_LIVE_REGION_ADDED
|
||||
"live region removed", // EVENT_LIVE_REGION_REMOVED
|
||||
"inner reorder", // EVENT_INNER_REORDER
|
||||
"live region changed", // EVENT_LIVE_REGION_CHANGED
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -213,10 +213,15 @@ interface nsIAccessibleEvent : nsISupports
|
||||
*/
|
||||
const unsigned long EVENT_INNER_REORDER = 0x0028;
|
||||
|
||||
/**
|
||||
* A live region's contents has changed. Mac Only.
|
||||
*/
|
||||
const unsigned long EVENT_LIVE_REGION_CHANGED = 0x0029;
|
||||
|
||||
/**
|
||||
* Help make sure event map does not get out-of-line.
|
||||
*/
|
||||
const unsigned long EVENT_LAST_ENTRY = 0x0029;
|
||||
const unsigned long EVENT_LAST_ENTRY = 0x002a;
|
||||
|
||||
/**
|
||||
* The type of event, based on the enumerated event values
|
||||
|
@ -54,6 +54,8 @@ class AccessibleWrap : public LocalAccessible {
|
||||
|
||||
virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
|
||||
|
||||
static bool IsLiveRegion(nsIContent* aContent);
|
||||
|
||||
protected:
|
||||
friend class xpcAccessibleMacInterface;
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,5 +161,19 @@ addAccessibleTask(
|
||||
});
|
||||
await liveRegionChanged;
|
||||
ok(true, "changed aria-label");
|
||||
|
||||
liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
content.document.getElementById("live").firstChild.data = "The hour is ";
|
||||
});
|
||||
await liveRegionChanged;
|
||||
ok(true, "changed text leaf contents");
|
||||
|
||||
liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
content.document.getElementById("live").firstChild.data = "";
|
||||
});
|
||||
await liveRegionChanged;
|
||||
ok(true, "delete text leaf contents");
|
||||
}
|
||||
);
|
||||
|
@ -53,5 +53,6 @@ static const uint32_t gWinEventMap[] = {
|
||||
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED
|
||||
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED
|
||||
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_INNER_REORDER
|
||||
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_LIVE_REGION_CHANGED
|
||||
// clang-format on
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user