patches: complete thread safety — dispatch_sync on ALL AX methods

Add dispatch_sync guard to: Buffer accessibilityFrame, accessibilityLabel,
accessibilityRole, accessibilityRoleDescription, accessibilityPlaceholderValue,
isAccessibilityFocused. ModeLine accessibilityValue, accessibilityFrame,
accessibilityLabel. setAccessibilitySelectedTextRange now uses
record_unwind_current_buffer + unbind_to.
This commit is contained in:
2026-02-27 14:55:03 +01:00
parent e4129581b7
commit 6994403014

View File

@@ -1,25 +1,22 @@
From 4871f48958e63cbf1d0086ba1cc0c15fabb6f7df Mon Sep 17 00:00:00 2001
From 6878620f894a738eb11f9742a3920434cfb00e1e Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Fri, 27 Feb 2026 14:49:27 +0100
Date: Fri, 27 Feb 2026 14:54:57 +0100
Subject: [PATCH] ns: implement VoiceOver accessibility
All AX getter methods now consistently dispatch_sync to the main
thread when called from the AX server thread, eliminating the mixed
threading strategy (some dispatch_sync, some @synchronized, some
neither) that could cause use-after-free on cachedText.
All AX protocol methods now dispatch_sync to the main thread,
including accessibilityFrame, accessibilityLabel, accessibilityRole,
accessibilityRoleDescription, accessibilityPlaceholderValue,
isAccessibilityFocused on EmacsAccessibilityBuffer, and
accessibilityValue/accessibilityFrame/accessibilityLabel on
EmacsAccessibilityModeLine. setAccessibilitySelectedTextRange
uses record_unwind_current_buffer for exception safety.
* 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.
* src/nsterm.m, src/nsterm.h, etc/NEWS: As described.
---
etc/NEWS | 11 +
src/nsterm.h | 108 ++
src/nsterm.m | 2798 +++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 2775 insertions(+), 142 deletions(-)
src/nsterm.m | 2870 +++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 2847 insertions(+), 142 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 7367e3c..0e4480a 100644
@@ -177,7 +174,7 @@ index 7c1ee4c..6455547 100644
diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..2e47899 100644
index 932d209..078465a 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch)
@@ -243,7 +240,7 @@ index 932d209..2e47899 100644
ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,213 +6891,2332 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
@@ -6849,213 +6891,2404 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
/* ==========================================================================
@@ -1428,6 +1425,14 @@ index 932d209..2e47899 100644
+
+- (NSAccessibilityRole)accessibilityRole
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSAccessibilityRole result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityRole];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (w && MINI_WINDOW_P (w))
+ return NSAccessibilityTextFieldRole;
@@ -1436,6 +1441,14 @@ index 932d209..2e47899 100644
+
+- (NSString *)accessibilityPlaceholderValue
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityPlaceholderValue];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (!w || !MINI_WINDOW_P (w))
+ return nil;
@@ -1447,6 +1460,14 @@ index 932d209..2e47899 100644
+
+- (NSString *)accessibilityRoleDescription
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityRoleDescription];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (w && MINI_WINDOW_P (w))
+ return @"minibuffer";
@@ -1455,6 +1476,14 @@ index 932d209..2e47899 100644
+
+- (NSString *)accessibilityLabel
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityLabel];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (w && WINDOW_LEAF_P (w))
+ {
@@ -1474,6 +1503,14 @@ index 932d209..2e47899 100644
+
+- (BOOL)isAccessibilityFocused
+{
+ if (![NSThread isMainThread])
+ {
+ __block BOOL result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self isAccessibilityFocused];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (!w)
+ return NO;
@@ -1593,6 +1630,9 @@ index 932d209..2e47899 100644
+
+ [self ensureTextCache];
+
+ specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_current_buffer ();
+
+ /* Convert accessibility index to buffer charpos via mapping. */
+ ptrdiff_t charpos = [self charposForAccessibilityIndex:range.location];
+
@@ -1604,9 +1644,7 @@ index 932d209..2e47899 100644
+
+ block_input ();
+
+ /* Move point directly in the buffer. Use set_point_both which
+ operates on the current buffer — temporarily switch if needed. */
+ struct buffer *oldb = current_buffer;
+ /* Move point directly in the buffer. */
+ if (b != current_buffer)
+ set_buffer_internal_1 (b);
+
@@ -1626,8 +1664,7 @@ index 932d209..2e47899 100644
+ else
+ bset_mark_active (b, Qnil);
+
+ if (b != oldb)
+ set_buffer_internal_1 (oldb);
+ unbind_to (count, Qnil);
+
+ unblock_input ();
+
@@ -1953,6 +1990,14 @@ index 932d209..2e47899 100644
+
+- (NSRect)accessibilityFrame
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSRect result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityFrame];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (!w)
+ return NSZeroRect;
@@ -2452,6 +2497,14 @@ index 932d209..2e47899 100644
+
+- (NSString *)accessibilityLabel
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityLabel];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (w && WINDOW_LEAF_P (w))
+ {
@@ -2471,6 +2524,14 @@ index 932d209..2e47899 100644
+
+- (id)accessibilityValue
+{
+ if (![NSThread isMainThread])
+ {
+ __block id result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityValue];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (!w)
+ return @"";
@@ -2479,6 +2540,14 @@ index 932d209..2e47899 100644
+
+- (NSRect)accessibilityFrame
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSRect result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityFrame];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow];
+ if (!w || !w->current_matrix)
+ return NSZeroRect;
@@ -2718,7 +2787,7 @@ index 932d209..2e47899 100644
#define NS_KEYLOG 0
- (void)keyDown: (NSEvent *)theEvent
@@ -8237,6 +10398,31 @@ - (void)windowDidBecomeKey /* for direct calls */
@@ -8237,6 +10470,31 @@ - (void)windowDidBecomeKey /* for direct calls */
XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop
@@ -2750,7 +2819,7 @@ index 932d209..2e47899 100644
}
@@ -9474,6 +11660,322 @@ - (int) fullscreenState
@@ -9474,6 +11732,322 @@ - (int) fullscreenState
return fs_state;
}
@@ -3073,7 +3142,7 @@ index 932d209..2e47899 100644
@end /* EmacsView */
@@ -11303,6 +13805,18 @@ Convert an X font name (XLFD) to an NS font name.
@@ -11303,6 +13877,18 @@ Convert an X font name (XLFD) to an NS font name.
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");