Filter out stale accessibility events.

This commit is contained in:
Storm Dragon committed 2026-11-04 14:23:54 -05:00
1 parent 9e15f8652d
commit f23e89ed93
10 files changed
+469 -2

No files matched your search

@@ -398,6 +398,15 @@ func (adapter *Adapter) enrichNativeWindowEvent(event *C.AtspiEvent, target *C.A
//export cthulhuAtspiEventCallback
func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
dispatch_native_event(event, userData, false, 0)
}
//export cthulhuAtspiSelectionCallback
func cthulhuAtspiSelectionCallback(event *C.AtspiEvent, userData unsafe.Pointer, epoch C.uint64_t) {
dispatch_native_event(event, userData, true, uint64(epoch))
}
func dispatch_native_event(event *C.AtspiEvent, userData unsafe.Pointer, deferredSelection bool, selectionEpoch uint64) {
if event == nil || userData == nil {
return
}
@@ -408,6 +417,9 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
return
}
secureEpoch := adapter.secureEpoch.Load()
if deferredSelection && (selectionEpoch != secureEpoch || adapter.secure.Load()) {
return
}
// Retire copied header identities even while locked. Tab lifecycle only
// checks roles; it never captures text or presents output.
nativeEventType := C.GoString(C.cthulhu_event_type(event))
@@ -422,10 +434,20 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
}
eventType := canonicalObjectChangeEventType(nativeEventType)
originID := nativeObjectID(event.source)
adapter.retireEchoFocus(eventType, int(C.cthulhu_event_detail1(event)), originID)
// Retired GTK cells can emit hundreds of obsolete focus/selection claims
// during a rebuild. Leave lifecycle retirement on its established path,
// but avoid all snapshot and foreground queries for known-dead targets.
switch eventType {
case FocusEvent, FocusedStateEvent, SelectedStateEvent, SelectionChangedEvent, ActiveDescendantEvent:
if C.cthulhu_accessible_known_dead(event.source) != 0 ||
eventType == ActiveDescendantEvent && C.cthulhu_accessible_known_dead(C.cthulhu_event_active_descendant(event)) != 0 {
return
}
}
if eventType == AccessibleNameEvent || eventType == DefunctStateEvent && C.cthulhu_event_detail1(event) != 0 {
C.cthulhu_forget_menu_checkability(event.source)
}
adapter.retireEchoFocus(eventType, int(C.cthulhu_event_detail1(event)), originID)
if eventType != DefunctStateEvent || C.cthulhu_event_detail1(event) != 0 {
adapter.retireLibreOfficeHeaders(event.source, eventType, originID)
}
@@ -438,6 +460,18 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
}
adapter.applySecureReset()
gtkSelection := eventType == SelectionChangedEvent && C.cthulhu_gtk_table(event.source, 0) != 0
// Structural events which only invalidate caches do not need a captured
// selection between every removed row. A structural event that survives
// invalidation below still flushes before its ordinary presentation path.
selectionBoundary := eventType != DefunctStateEvent && !strings.HasPrefix(eventType, ChildrenChangedEvent)
if !deferredSelection && selectionBoundary {
if gtkSelection {
C.cthulhu_flush_other_selection(adapter.callbackData, event.source)
} else {
C.cthulhu_flush_selection(adapter.callbackData)
}
}
adapter.refreshForeground()
if isDocumentLifecycleEvent(eventType) || eventType == DefunctStateEvent && C.cthulhu_event_detail1(event) != 0 {
adapter.deferredLiveRegions.take(originID)
@@ -505,6 +539,13 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
adapter.backgroundEventsFiltered.Add(1)
return
}
// Seed an unknown toolkit identity only after foreground filtering, so a
// background provider never delays this path with a toolkit lookup.
if !deferredSelection && eventType == SelectionChangedEvent &&
(gtkSelection || C.cthulhu_gtk_table(event.source, 1) != 0) {
C.cthulhu_defer_selection(adapter.callbackData, event, C.uint64_t(secureEpoch))
return
}
if tab := addedSelectedBrowserTab(event, eventType); tab != nil {
defer C.g_object_unref(C.gpointer(tab))
returnedTarget = tab
@@ -517,6 +558,9 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
if invalidated && webNotification == nil && eventType == ChildrenAddedEvent && (live.politeness == "" || live.politeness == "off") {
return
}
if !deferredSelection && !selectionBoundary {
C.cthulhu_flush_selection(adapter.callbackData)
}
deferLive, completeLive := adapter.prepare_live_region(event, eventType, originID, live)
if deferLive {
return
@@ -532,6 +576,13 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
if target == nil {
return
}
if (eventType == FocusEvent || eventType == FocusedStateEvent || eventType == SelectedStateEvent ||
eventType == ActiveDescendantEvent || eventType == SelectionChangedEvent) && C.cthulhu_accessible_known_dead(target) != 0 {
if ownedTarget {
C.g_object_unref(C.gpointer(target))
}
return
}
refreshNativeEventTarget(event, target, eventType)
snapshot, popupFor, payload := adapter.captureNativeEventContent(event, target, eventType, originID, live, completeLive)
eventType, inlineMenuID := normalizeNativeMenuFocus(event, target, eventType, originID, &snapshot)