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> 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 Subject: [PATCH] ns: implement VoiceOver accessibility
All AX getter methods now consistently dispatch_sync to the main All AX protocol methods now dispatch_sync to the main thread,
thread when called from the AX server thread, eliminating the mixed including accessibilityFrame, accessibilityLabel, accessibilityRole,
threading strategy (some dispatch_sync, some @synchronized, some accessibilityRoleDescription, accessibilityPlaceholderValue,
neither) that could cause use-after-free on cachedText. 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, * src/nsterm.m, src/nsterm.h, etc/NEWS: As described.
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 | 11 + etc/NEWS | 11 +
src/nsterm.h | 108 ++ src/nsterm.h | 108 ++
src/nsterm.m | 2798 +++++++++++++++++++++++++++++++++++++++++++++++--- src/nsterm.m | 2870 +++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 2775 insertions(+), 142 deletions(-) 3 files changed, 2847 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
@@ -177,7 +174,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..2e47899 100644 index 932d209..078465a 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)
@@ -243,7 +240,7 @@ index 932d209..2e47899 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; 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 +- (NSAccessibilityRole)accessibilityRole
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSAccessibilityRole result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityRole];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (w && MINI_WINDOW_P (w)) + if (w && MINI_WINDOW_P (w))
+ return NSAccessibilityTextFieldRole; + return NSAccessibilityTextFieldRole;
@@ -1436,6 +1441,14 @@ index 932d209..2e47899 100644
+ +
+- (NSString *)accessibilityPlaceholderValue +- (NSString *)accessibilityPlaceholderValue
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityPlaceholderValue];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (!w || !MINI_WINDOW_P (w)) + if (!w || !MINI_WINDOW_P (w))
+ return nil; + return nil;
@@ -1447,6 +1460,14 @@ index 932d209..2e47899 100644
+ +
+- (NSString *)accessibilityRoleDescription +- (NSString *)accessibilityRoleDescription
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityRoleDescription];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (w && MINI_WINDOW_P (w)) + if (w && MINI_WINDOW_P (w))
+ return @"minibuffer"; + return @"minibuffer";
@@ -1455,6 +1476,14 @@ index 932d209..2e47899 100644
+ +
+- (NSString *)accessibilityLabel +- (NSString *)accessibilityLabel
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityLabel];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (w && WINDOW_LEAF_P (w)) + if (w && WINDOW_LEAF_P (w))
+ { + {
@@ -1474,6 +1503,14 @@ index 932d209..2e47899 100644
+ +
+- (BOOL)isAccessibilityFocused +- (BOOL)isAccessibilityFocused
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block BOOL result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self isAccessibilityFocused];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (!w) + if (!w)
+ return NO; + return NO;
@@ -1593,6 +1630,9 @@ index 932d209..2e47899 100644
+ +
+ [self ensureTextCache]; + [self ensureTextCache];
+ +
+ specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_current_buffer ();
+
+ /* Convert accessibility index to buffer charpos via mapping. */ + /* Convert accessibility index to buffer charpos via mapping. */
+ ptrdiff_t charpos = [self charposForAccessibilityIndex:range.location]; + ptrdiff_t charpos = [self charposForAccessibilityIndex:range.location];
+ +
@@ -1604,9 +1644,7 @@ index 932d209..2e47899 100644
+ +
+ block_input (); + block_input ();
+ +
+ /* Move point directly in the buffer. Use set_point_both which + /* Move point directly in the buffer. */
+ operates on the current buffer — temporarily switch if needed. */
+ struct buffer *oldb = current_buffer;
+ if (b != current_buffer) + if (b != current_buffer)
+ set_buffer_internal_1 (b); + set_buffer_internal_1 (b);
+ +
@@ -1626,8 +1664,7 @@ index 932d209..2e47899 100644
+ else + else
+ bset_mark_active (b, Qnil); + bset_mark_active (b, Qnil);
+ +
+ if (b != oldb) + unbind_to (count, Qnil);
+ set_buffer_internal_1 (oldb);
+ +
+ unblock_input (); + unblock_input ();
+ +
@@ -1953,6 +1990,14 @@ index 932d209..2e47899 100644
+ +
+- (NSRect)accessibilityFrame +- (NSRect)accessibilityFrame
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSRect result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityFrame];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (!w) + if (!w)
+ return NSZeroRect; + return NSZeroRect;
@@ -2452,6 +2497,14 @@ index 932d209..2e47899 100644
+ +
+- (NSString *)accessibilityLabel +- (NSString *)accessibilityLabel
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityLabel];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (w && WINDOW_LEAF_P (w)) + if (w && WINDOW_LEAF_P (w))
+ { + {
@@ -2471,6 +2524,14 @@ index 932d209..2e47899 100644
+ +
+- (id)accessibilityValue +- (id)accessibilityValue
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block id result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityValue];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (!w) + if (!w)
+ return @""; + return @"";
@@ -2479,6 +2540,14 @@ index 932d209..2e47899 100644
+ +
+- (NSRect)accessibilityFrame +- (NSRect)accessibilityFrame
+{ +{
+ if (![NSThread isMainThread])
+ {
+ __block NSRect result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityFrame];
+ });
+ return result;
+ }
+ struct window *w = [self validWindow]; + struct window *w = [self validWindow];
+ if (!w || !w->current_matrix) + if (!w || !w->current_matrix)
+ return NSZeroRect; + return NSZeroRect;
@@ -2718,7 +2787,7 @@ index 932d209..2e47899 100644
#define NS_KEYLOG 0 #define NS_KEYLOG 0
- (void)keyDown: (NSEvent *)theEvent - (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); 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
@@ -2750,7 +2819,7 @@ index 932d209..2e47899 100644
} }
@@ -9474,6 +11660,322 @@ - (int) fullscreenState @@ -9474,6 +11732,322 @@ - (int) fullscreenState
return fs_state; return fs_state;
} }
@@ -3073,7 +3142,7 @@ index 932d209..2e47899 100644
@end /* EmacsView */ @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_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");