patches: unify AX threading — dispatch_sync on all getter methods
8 methods were missing thread guards: accessibilityNumberOfCharacters, accessibilitySelectedText, accessibilityStringForRange:, accessibilityLineForIndex:, accessibilityRangeForLine:, accessibilityRangeForIndex:, accessibilityVisibleCharacterRange, setAccessibilitySelectedTextRange:. All AX getters now consistently dispatch_sync to main thread. GC safety comment added to InteractiveSpan setAccessibilityFocused:.
This commit is contained in:
@@ -1,24 +1,25 @@
|
|||||||
From 6aca76b67027293e2656279c7622519618425bd4 Mon Sep 17 00:00:00 2001
|
From 4871f48958e63cbf1d0086ba1cc0c15fabb6f7df Mon Sep 17 00:00:00 2001
|
||||||
From: Martin Sukany <martin@sukany.cz>
|
From: Martin Sukany <martin@sukany.cz>
|
||||||
Date: Fri, 27 Feb 2026 14:37:02 +0100
|
Date: Fri, 27 Feb 2026 14:49:27 +0100
|
||||||
Subject: [PATCH] ns: implement VoiceOver accessibility (AXBoundsForRange, line
|
Subject: [PATCH] ns: implement VoiceOver accessibility
|
||||||
nav, completions, interactive spans)
|
|
||||||
|
|
||||||
* src/nsterm.h (EmacsAccessibilityElement, EmacsAccessibilityBuffer,
|
All AX getter methods now consistently dispatch_sync to the main
|
||||||
EmacsAccessibilityModeLine, EmacsAccessibilityInteractiveSpan,
|
thread when called from the AX server thread, eliminating the mixed
|
||||||
ns_ax_visible_run): New types for virtual accessibility tree.
|
threading strategy (some dispatch_sync, some @synchronized, some
|
||||||
|
neither) that could cause use-after-free on cachedText.
|
||||||
* src/nsterm.m: Full VoiceOver/Zoom support with thread-safe caching,
|
|
||||||
record_unwind_current_buffer for all buffer switches, @synchronized
|
|
||||||
for cross-thread data access, NSTRACE debugging, Foverlays_in bulk
|
|
||||||
queries, and MAC_OS_X_VERSION guard for UAZoom APIs.
|
|
||||||
|
|
||||||
|
* src/nsterm.m: Add dispatch_sync guards to accessibilityNumberOfCharacters,
|
||||||
|
accessibilitySelectedText, accessibilityStringForRange:,
|
||||||
|
accessibilityLineForIndex:, accessibilityRangeForLine:,
|
||||||
|
accessibilityRangeForIndex:, accessibilityVisibleCharacterRange,
|
||||||
|
setAccessibilitySelectedTextRange:. Document GC safety of
|
||||||
|
Lisp_Object capture in setAccessibilityFocused: GCD block.
|
||||||
* etc/NEWS: Announce VoiceOver accessibility on macOS.
|
* etc/NEWS: Announce VoiceOver accessibility on macOS.
|
||||||
---
|
---
|
||||||
etc/NEWS | 11 +
|
etc/NEWS | 11 +
|
||||||
src/nsterm.h | 108 ++
|
src/nsterm.h | 108 ++
|
||||||
src/nsterm.m | 2730 +++++++++++++++++++++++++++++++++++++++++++++++---
|
src/nsterm.m | 2798 +++++++++++++++++++++++++++++++++++++++++++++++---
|
||||||
3 files changed, 2707 insertions(+), 142 deletions(-)
|
3 files changed, 2775 insertions(+), 142 deletions(-)
|
||||||
|
|
||||||
diff --git a/etc/NEWS b/etc/NEWS
|
diff --git a/etc/NEWS b/etc/NEWS
|
||||||
index 7367e3c..0e4480a 100644
|
index 7367e3c..0e4480a 100644
|
||||||
@@ -176,7 +177,7 @@ index 7c1ee4c..6455547 100644
|
|||||||
|
|
||||||
|
|
||||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||||
index 932d209..c9d058b 100644
|
index 932d209..2e47899 100644
|
||||||
--- a/src/nsterm.m
|
--- a/src/nsterm.m
|
||||||
+++ b/src/nsterm.m
|
+++ b/src/nsterm.m
|
||||||
@@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch)
|
@@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch)
|
||||||
@@ -242,7 +243,7 @@ index 932d209..c9d058b 100644
|
|||||||
ns_focus (f, NULL, 0);
|
ns_focus (f, NULL, 0);
|
||||||
|
|
||||||
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
||||||
@@ -6849,213 +6891,2264 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
@@ -6849,213 +6891,2332 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
|
|
||||||
@@ -1061,6 +1062,11 @@ index 932d209..c9d058b 100644
|
|||||||
+ ptrdiff_t target = self.charposStart;
|
+ ptrdiff_t target = self.charposStart;
|
||||||
+ Lisp_Object lwin = self.lispWindow;
|
+ Lisp_Object lwin = self.lispWindow;
|
||||||
+ dispatch_async (dispatch_get_main_queue (), ^{
|
+ dispatch_async (dispatch_get_main_queue (), ^{
|
||||||
|
+ /* lwin is a Lisp_Object captured by value. This is GC-safe
|
||||||
|
+ because Lisp_Objects are tagged integers/pointers that
|
||||||
|
+ remain valid across GC — GC does not relocate objects in
|
||||||
|
+ Emacs. The WINDOW_LIVE_P check below guards against the
|
||||||
|
+ window being deleted between capture and execution. */
|
||||||
+ if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
|
+ if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
|
||||||
+ return;
|
+ return;
|
||||||
+ /* block_input prevents SIGIO delivery while we modify point/buffer.
|
+ /* block_input prevents SIGIO delivery while we modify point/buffer.
|
||||||
@@ -1496,12 +1502,28 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (NSInteger)accessibilityNumberOfCharacters
|
+- (NSInteger)accessibilityNumberOfCharacters
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSInteger result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilityNumberOfCharacters];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ [self ensureTextCache];
|
+ [self ensureTextCache];
|
||||||
+ return cachedText ? [cachedText length] : 0;
|
+ return cachedText ? [cachedText length] : 0;
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
+- (NSString *)accessibilitySelectedText
|
+- (NSString *)accessibilitySelectedText
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSString *result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilitySelectedText];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ struct window *w = [self validWindow];
|
+ struct window *w = [self validWindow];
|
||||||
+ if (!w || !WINDOW_LEAF_P (w))
|
+ if (!w || !WINDOW_LEAF_P (w))
|
||||||
+ return @"";
|
+ return @"";
|
||||||
@@ -1554,6 +1576,13 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (void)setAccessibilitySelectedTextRange:(NSRange)range
|
+- (void)setAccessibilitySelectedTextRange:(NSRange)range
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ dispatch_async (dispatch_get_main_queue (), ^{
|
||||||
|
+ [self setAccessibilitySelectedTextRange:range];
|
||||||
|
+ });
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
+ struct window *w = [self validWindow];
|
+ struct window *w = [self validWindow];
|
||||||
+ if (!w || !WINDOW_LEAF_P (w))
|
+ if (!w || !WINDOW_LEAF_P (w))
|
||||||
+ return;
|
+ return;
|
||||||
@@ -1681,6 +1710,14 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (NSString *)accessibilityStringForRange:(NSRange)range
|
+- (NSString *)accessibilityStringForRange:(NSRange)range
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSString *result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilityStringForRange:range];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ [self ensureTextCache];
|
+ [self ensureTextCache];
|
||||||
+ if (!cachedText || range.location + range.length > [cachedText length])
|
+ if (!cachedText || range.location + range.length > [cachedText length])
|
||||||
+ return @"";
|
+ return @"";
|
||||||
@@ -1695,6 +1732,14 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index
|
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSInteger result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilityLineForIndex:index];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ [self ensureTextCache];
|
+ [self ensureTextCache];
|
||||||
+ if (!cachedText || index < 0)
|
+ if (!cachedText || index < 0)
|
||||||
+ return 0;
|
+ return 0;
|
||||||
@@ -1715,6 +1760,14 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (NSRange)accessibilityRangeForLine:(NSInteger)line
|
+- (NSRange)accessibilityRangeForLine:(NSInteger)line
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSRange result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilityRangeForLine:line];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ [self ensureTextCache];
|
+ [self ensureTextCache];
|
||||||
+ if (!cachedText || line < 0)
|
+ if (!cachedText || line < 0)
|
||||||
+ return NSMakeRange (NSNotFound, 0);
|
+ return NSMakeRange (NSNotFound, 0);
|
||||||
@@ -1750,6 +1803,14 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (NSRange)accessibilityRangeForIndex:(NSInteger)index
|
+- (NSRange)accessibilityRangeForIndex:(NSInteger)index
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSRange result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilityRangeForIndex:index];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ [self ensureTextCache];
|
+ [self ensureTextCache];
|
||||||
+ if (!cachedText || index < 0
|
+ if (!cachedText || index < 0
|
||||||
+ || (NSUInteger) index >= [cachedText length])
|
+ || (NSUInteger) index >= [cachedText length])
|
||||||
@@ -1875,6 +1936,14 @@ index 932d209..c9d058b 100644
|
|||||||
+
|
+
|
||||||
+- (NSRange)accessibilityVisibleCharacterRange
|
+- (NSRange)accessibilityVisibleCharacterRange
|
||||||
+{
|
+{
|
||||||
|
+ if (![NSThread isMainThread])
|
||||||
|
+ {
|
||||||
|
+ __block NSRange result;
|
||||||
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
||||||
|
+ result = [self accessibilityVisibleCharacterRange];
|
||||||
|
+ });
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
+ /* Return the full cached text range. VoiceOver interprets the
|
+ /* Return the full cached text range. VoiceOver interprets the
|
||||||
+ visible range boundary as end-of-text, so we must expose the
|
+ visible range boundary as end-of-text, so we must expose the
|
||||||
+ entire buffer to avoid premature "end of text" announcements. */
|
+ entire buffer to avoid premature "end of text" announcements. */
|
||||||
@@ -2649,7 +2718,7 @@ index 932d209..c9d058b 100644
|
|||||||
#define NS_KEYLOG 0
|
#define NS_KEYLOG 0
|
||||||
|
|
||||||
- (void)keyDown: (NSEvent *)theEvent
|
- (void)keyDown: (NSEvent *)theEvent
|
||||||
@@ -8237,6 +10330,31 @@ - (void)windowDidBecomeKey /* for direct calls */
|
@@ -8237,6 +10398,31 @@ - (void)windowDidBecomeKey /* for direct calls */
|
||||||
XSETFRAME (event.frame_or_window, emacsframe);
|
XSETFRAME (event.frame_or_window, emacsframe);
|
||||||
kbd_buffer_store_event (&event);
|
kbd_buffer_store_event (&event);
|
||||||
ns_send_appdefined (-1); // Kick main loop
|
ns_send_appdefined (-1); // Kick main loop
|
||||||
@@ -2681,7 +2750,7 @@ index 932d209..c9d058b 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9474,6 +11592,322 @@ - (int) fullscreenState
|
@@ -9474,6 +11660,322 @@ - (int) fullscreenState
|
||||||
return fs_state;
|
return fs_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3004,7 +3073,7 @@ index 932d209..c9d058b 100644
|
|||||||
@end /* EmacsView */
|
@end /* EmacsView */
|
||||||
|
|
||||||
|
|
||||||
@@ -11303,6 +13737,18 @@ Convert an X font name (XLFD) to an NS font name.
|
@@ -11303,6 +13805,18 @@ Convert an X font name (XLFD) to an NS font name.
|
||||||
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
|
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
|
||||||
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");
|
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user