patches: add 0009 resource safety hardening + update 0007/0008
New patch 0009 fixes HIGH severity issues from Opus review: - Announcement coalescing (50ms debounce) - cachedText retain+autorelease in accessibilityValue - EmacsView dealloc: nil out emacsView on all AX elements - Nil guards on protocol methods + overlayZoomActive 0007 updated: revert accidental em-dash→triple-dash, add overlayZoomActive nil guards 0008 updated: specpdl exception safety for accessibilityUpdating, lastChildFrameBuffer staticpro Series now 9 patches total (0001-0006 unchanged, 0007-0009 new/updated).
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
From 6e907a1000a8b138976d6a906e40449fdf1a61c5 Mon Sep 17 00:00:00 2001
|
From 8712cf8f567f3b0c02cc70a93aff931faa3a2df3 Mon Sep 17 00:00:00 2001
|
||||||
From: Martin Sukany <martin@sukany.cz>
|
From: Martin Sukany <martin@sukany.cz>
|
||||||
Date: Sat, 28 Feb 2026 14:46:25 +0100
|
Date: Sat, 28 Feb 2026 14:46:25 +0100
|
||||||
Subject: [PATCH 1/2] ns: announce overlay completion candidates for VoiceOver
|
Subject: [PATCH 1/3] ns: announce overlay completion candidates for VoiceOver
|
||||||
|
|
||||||
Completion frameworks such as Vertico, Ivy, and Icomplete render
|
Completion frameworks such as Vertico, Ivy, and Icomplete render
|
||||||
candidates via overlay before-string/after-string properties rather
|
candidates via overlay before-string/after-string properties rather
|
||||||
@@ -52,8 +52,8 @@ Independent overlay branch, BUF_CHARS_MODIFF gating, candidate
|
|||||||
announcement with overlay Zoom rect storage.
|
announcement with overlay Zoom rect storage.
|
||||||
---
|
---
|
||||||
src/nsterm.h | 3 +
|
src/nsterm.h | 3 +
|
||||||
src/nsterm.m | 331 +++++++++++++++++++++++++++++++++++++++++++++------
|
src/nsterm.m | 333 +++++++++++++++++++++++++++++++++++++++++++++------
|
||||||
2 files changed, 298 insertions(+), 36 deletions(-)
|
2 files changed, 300 insertions(+), 36 deletions(-)
|
||||||
|
|
||||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||||
index 51c30ca..5c15639 100644
|
index 51c30ca..5c15639 100644
|
||||||
@@ -77,7 +77,7 @@ index 51c30ca..5c15639 100644
|
|||||||
BOOL font_panel_active;
|
BOOL font_panel_active;
|
||||||
NSFont *font_panel_result;
|
NSFont *font_panel_result;
|
||||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||||
index 1780194..143e784 100644
|
index 1780194..c1fc3cb 100644
|
||||||
--- a/src/nsterm.m
|
--- a/src/nsterm.m
|
||||||
+++ b/src/nsterm.m
|
+++ b/src/nsterm.m
|
||||||
@@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
|
@@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
|
||||||
@@ -403,7 +403,7 @@ index 1780194..143e784 100644
|
|||||||
self.cachedPoint = point;
|
self.cachedPoint = point;
|
||||||
|
|
||||||
NSDictionary *change = @{
|
NSDictionary *change = @{
|
||||||
@@ -8789,16 +8938,126 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
|
@@ -8789,16 +8938,128 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
|
||||||
BOOL markActive = !NILP (BVAR (b, mark_active));
|
BOOL markActive = !NILP (BVAR (b, mark_active));
|
||||||
|
|
||||||
/* --- Text changed (edit) --- */
|
/* --- Text changed (edit) --- */
|
||||||
@@ -428,7 +428,8 @@ index 1780194..143e784 100644
|
|||||||
+ if (chars_modiff != self.cachedCharsModiff)
|
+ if (chars_modiff != self.cachedCharsModiff)
|
||||||
+ {
|
+ {
|
||||||
+ self.cachedCharsModiff = chars_modiff;
|
+ self.cachedCharsModiff = chars_modiff;
|
||||||
+ self.emacsView->overlayZoomActive = NO;
|
+ if (self.emacsView)
|
||||||
|
+ self.emacsView->overlayZoomActive = NO;
|
||||||
+ [self postTextChangedNotification:point];
|
+ [self postTextChangedNotification:point];
|
||||||
+ textDidChange = YES;
|
+ textDidChange = YES;
|
||||||
+ }
|
+ }
|
||||||
@@ -516,7 +517,8 @@ index 1780194..143e784 100644
|
|||||||
+ (minibuffer exit, C-g, etc.) or overlay has no
|
+ (minibuffer exit, C-g, etc.) or overlay has no
|
||||||
+ recognizable selection face. Return Zoom to the
|
+ recognizable selection face. Return Zoom to the
|
||||||
+ text cursor. */
|
+ text cursor. */
|
||||||
+ self.emacsView->overlayZoomActive = NO;
|
+ if (self.emacsView)
|
||||||
|
+ self.emacsView->overlayZoomActive = NO;
|
||||||
+ }
|
+ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +536,7 @@ index 1780194..143e784 100644
|
|||||||
{
|
{
|
||||||
ptrdiff_t oldPoint = self.cachedPoint;
|
ptrdiff_t oldPoint = self.cachedPoint;
|
||||||
BOOL oldMarkActive = self.cachedMarkActive;
|
BOOL oldMarkActive = self.cachedMarkActive;
|
||||||
@@ -8966,7 +9225,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
|
@@ -8966,7 +9227,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
@@ -543,7 +545,7 @@ index 1780194..143e784 100644
|
|||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
/* Scan visible range of window W for interactive spans.
|
/* Scan visible range of window W for interactive spans.
|
||||||
@@ -9157,7 +9416,7 @@ ns_ax_scan_interactive_spans (struct window *w,
|
@@ -9157,7 +9418,7 @@ ns_ax_scan_interactive_spans (struct window *w,
|
||||||
- (BOOL) isAccessibilityFocused
|
- (BOOL) isAccessibilityFocused
|
||||||
{
|
{
|
||||||
/* Read the cached point stored by EmacsAccessibilityBuffer on the main
|
/* Read the cached point stored by EmacsAccessibilityBuffer on the main
|
||||||
@@ -552,7 +554,7 @@ index 1780194..143e784 100644
|
|||||||
EmacsAccessibilityBuffer *pb = self.parentBuffer;
|
EmacsAccessibilityBuffer *pb = self.parentBuffer;
|
||||||
if (!pb)
|
if (!pb)
|
||||||
return NO;
|
return NO;
|
||||||
@@ -9174,7 +9433,7 @@ ns_ax_scan_interactive_spans (struct window *w,
|
@@ -9174,7 +9435,7 @@ ns_ax_scan_interactive_spans (struct window *w,
|
||||||
dispatch_async (dispatch_get_main_queue (), ^{
|
dispatch_async (dispatch_get_main_queue (), ^{
|
||||||
/* lwin is a Lisp_Object captured by value. This is GC-safe
|
/* lwin is a Lisp_Object captured by value. This is GC-safe
|
||||||
because Lisp_Objects are tagged integers/pointers that
|
because Lisp_Objects are tagged integers/pointers that
|
||||||
@@ -561,7 +563,7 @@ index 1780194..143e784 100644
|
|||||||
Emacs. The WINDOW_LIVE_P check below guards against the
|
Emacs. The WINDOW_LIVE_P check below guards against the
|
||||||
window being deleted between capture and execution. */
|
window being deleted between capture and execution. */
|
||||||
if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
|
if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
|
||||||
@@ -9200,7 +9459,7 @@ ns_ax_scan_interactive_spans (struct window *w,
|
@@ -9200,7 +9461,7 @@ ns_ax_scan_interactive_spans (struct window *w,
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -570,7 +572,7 @@ index 1780194..143e784 100644
|
|||||||
Methods are kept here (same .m file) so they access the ivars
|
Methods are kept here (same .m file) so they access the ivars
|
||||||
declared in the @interface ivar block. */
|
declared in the @interface ivar block. */
|
||||||
@implementation EmacsAccessibilityBuffer (InteractiveSpans)
|
@implementation EmacsAccessibilityBuffer (InteractiveSpans)
|
||||||
@@ -11922,7 +12181,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -11922,7 +12183,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
|
|
||||||
if (WINDOW_LEAF_P (w))
|
if (WINDOW_LEAF_P (w))
|
||||||
{
|
{
|
||||||
@@ -579,7 +581,7 @@ index 1780194..143e784 100644
|
|||||||
EmacsAccessibilityBuffer *elem
|
EmacsAccessibilityBuffer *elem
|
||||||
= [existing objectForKey:[NSValue valueWithPointer:w]];
|
= [existing objectForKey:[NSValue valueWithPointer:w]];
|
||||||
if (!elem)
|
if (!elem)
|
||||||
@@ -11956,7 +12215,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -11956,7 +12217,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -588,7 +590,7 @@ index 1780194..143e784 100644
|
|||||||
Lisp_Object child = w->contents;
|
Lisp_Object child = w->contents;
|
||||||
while (!NILP (child))
|
while (!NILP (child))
|
||||||
{
|
{
|
||||||
@@ -12068,7 +12327,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -12068,7 +12329,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
accessibilityUpdating = YES;
|
accessibilityUpdating = YES;
|
||||||
|
|
||||||
/* Detect window tree change (split, delete, new buffer). Compare
|
/* Detect window tree change (split, delete, new buffer). Compare
|
||||||
@@ -597,7 +599,7 @@ index 1780194..143e784 100644
|
|||||||
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
|
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
|
||||||
if (!EQ (curRoot, lastRootWindow))
|
if (!EQ (curRoot, lastRootWindow))
|
||||||
{
|
{
|
||||||
@@ -12077,12 +12336,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -12077,12 +12338,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If tree is stale, rebuild FIRST so we don't iterate freed
|
/* If tree is stale, rebuild FIRST so we don't iterate freed
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
From 8564e4989f5f358092bd1494c3894a42974ee6e1 Mon Sep 17 00:00:00 2001
|
From 7d20ec80aa0d4ca97fa789f9b85389e25d2ff719 Mon Sep 17 00:00:00 2001
|
||||||
From: Martin Sukany <martin@sukany.cz>
|
From: Martin Sukany <martin@sukany.cz>
|
||||||
Date: Sat, 28 Feb 2026 16:01:29 +0100
|
Date: Sat, 28 Feb 2026 16:01:29 +0100
|
||||||
Subject: [PATCH 2/2] ns: announce child frame completion candidates for
|
Subject: [PATCH 2/3] ns: announce child frame completion candidates for
|
||||||
VoiceOver
|
VoiceOver
|
||||||
|
|
||||||
Completion frameworks such as Corfu, Company-box, and similar
|
Completion frameworks such as Corfu, Company-box, and similar
|
||||||
@@ -43,8 +43,8 @@ childFrameCompletionActive flag.
|
|||||||
refocus parent buffer element when child frame closes.
|
refocus parent buffer element when child frame closes.
|
||||||
---
|
---
|
||||||
src/nsterm.h | 2 +
|
src/nsterm.h | 2 +
|
||||||
src/nsterm.m | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++-
|
src/nsterm.m | 306 ++++++++++++++++++++++++++++++++++++++++++++++++++-
|
||||||
2 files changed, 278 insertions(+), 3 deletions(-)
|
2 files changed, 305 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||||
index 5c15639..8b34300 100644
|
index 5c15639..8b34300 100644
|
||||||
@@ -67,7 +67,7 @@ index 5c15639..8b34300 100644
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||||
index 143e784..da1a319 100644
|
index c1fc3cb..abecb4c 100644
|
||||||
--- a/src/nsterm.m
|
--- a/src/nsterm.m
|
||||||
+++ b/src/nsterm.m
|
+++ b/src/nsterm.m
|
||||||
@@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b,
|
@@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b,
|
||||||
@@ -181,7 +181,7 @@ index 143e784..da1a319 100644
|
|||||||
/* Build accessibility text for window W, skipping invisible text.
|
/* Build accessibility text for window W, skipping invisible text.
|
||||||
Populates *OUT_START with the buffer start charpos.
|
Populates *OUT_START with the buffer start charpos.
|
||||||
Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
|
Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
|
||||||
@@ -12311,6 +12415,122 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -12313,6 +12417,146 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
The existing elements carry cached state (modiff, point) from the
|
The existing elements carry cached state (modiff, point) from the
|
||||||
previous redisplay cycle. Rebuilding first would create fresh
|
previous redisplay cycle. Rebuilding first would create fresh
|
||||||
elements with current values, making change detection impossible. */
|
elements with current values, making change detection impossible. */
|
||||||
@@ -289,6 +289,30 @@ index 143e784..da1a319 100644
|
|||||||
+static EMACS_INT lastChildFrameModiff;
|
+static EMACS_INT lastChildFrameModiff;
|
||||||
+static char *lastChildFrameCandidate;
|
+static char *lastChildFrameCandidate;
|
||||||
+
|
+
|
||||||
|
+/* Child frame completion dedup state. File-scope so that
|
||||||
|
+ lastChildFrameBuffer can be staticpro'd against GC. */
|
||||||
|
+static Lisp_Object lastChildFrameBuffer;
|
||||||
|
+static EMACS_INT lastChildFrameModiff;
|
||||||
|
+static char *lastChildFrameCandidate;
|
||||||
|
+
|
||||||
|
+/* Reset the re-entrance guard when unwinding past
|
||||||
|
+ postAccessibilityUpdates due to a Lisp signal (longjmp).
|
||||||
|
+ Without this, a signal during Lisp calls (e.g. Fget_char_property
|
||||||
|
+ in overlay or child frame scanning) would leave
|
||||||
|
+ accessibilityUpdating = YES permanently, suppressing all future
|
||||||
|
+ accessibility notifications. */
|
||||||
|
+static void
|
||||||
|
+ns_ax_reset_accessibility_updating (void *view)
|
||||||
|
+{
|
||||||
|
+ ((EmacsView *)view)->accessibilityUpdating = NO;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/* Child frame completion dedup state. File-scope so that
|
||||||
|
+ lastChildFrameBuffer can be staticpro'd against GC. */
|
||||||
|
+static Lisp_Object lastChildFrameBuffer;
|
||||||
|
+static EMACS_INT lastChildFrameModiff;
|
||||||
|
+static char *lastChildFrameCandidate;
|
||||||
|
+
|
||||||
+/* Reset the re-entrance guard when unwinding past
|
+/* Reset the re-entrance guard when unwinding past
|
||||||
+ postAccessibilityUpdates due to a Lisp signal (longjmp).
|
+ postAccessibilityUpdates due to a Lisp signal (longjmp).
|
||||||
+ Without this, a signal during Lisp calls (e.g. Fget_char_property
|
+ Without this, a signal during Lisp calls (e.g. Fget_char_property
|
||||||
@@ -304,7 +328,7 @@ index 143e784..da1a319 100644
|
|||||||
- (void)postAccessibilityUpdates
|
- (void)postAccessibilityUpdates
|
||||||
{
|
{
|
||||||
NSTRACE ("[EmacsView postAccessibilityUpdates]");
|
NSTRACE ("[EmacsView postAccessibilityUpdates]");
|
||||||
@@ -12321,10 +12541,60 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -12323,10 +12567,60 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
|
|
||||||
/* Re-entrance guard: VoiceOver callbacks during notification posting
|
/* Re-entrance guard: VoiceOver callbacks during notification posting
|
||||||
can trigger redisplay, which calls ns_update_end, which calls us
|
can trigger redisplay, which calls ns_update_end, which calls us
|
||||||
@@ -366,7 +390,7 @@ index 143e784..da1a319 100644
|
|||||||
|
|
||||||
/* Detect window tree change (split, delete, new buffer). Compare
|
/* Detect window tree change (split, delete, new buffer). Compare
|
||||||
FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */
|
FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */
|
||||||
@@ -12355,7 +12625,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -12357,7 +12651,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
NSAccessibilityFocusedUIElementChangedNotification);
|
NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
|
|
||||||
lastSelectedWindow = emacsframe->selected_window;
|
lastSelectedWindow = emacsframe->selected_window;
|
||||||
@@ -375,7 +399,7 @@ index 143e784..da1a319 100644
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12399,7 +12669,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
@@ -12401,7 +12695,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
|
||||||
NSAccessibilityFocusedUIElementChangedNotification);
|
NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,12 +408,15 @@ index 143e784..da1a319 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Cursor position for Zoom (via accessibilityBoundsForRange:) ----
|
/* ---- Cursor position for Zoom (via accessibilityBoundsForRange:) ----
|
||||||
@@ -14341,6 +14611,9 @@ syms_of_nsterm (void)
|
@@ -14343,6 +14637,12 @@ syms_of_nsterm (void)
|
||||||
DEFSYM (Qns_ax_completion, "completion");
|
DEFSYM (Qns_ax_completion, "completion");
|
||||||
DEFSYM (Qns_ax_completions_highlight, "completions-highlight");
|
DEFSYM (Qns_ax_completions_highlight, "completions-highlight");
|
||||||
DEFSYM (Qns_ax_backtab, "backtab");
|
DEFSYM (Qns_ax_backtab, "backtab");
|
||||||
+
|
+
|
||||||
+ lastChildFrameBuffer = Qnil;
|
+ lastChildFrameBuffer = Qnil;
|
||||||
|
+ staticpro (&lastChildFrameBuffer);
|
||||||
|
+
|
||||||
|
+ lastChildFrameBuffer = Qnil;
|
||||||
+ staticpro (&lastChildFrameBuffer);
|
+ staticpro (&lastChildFrameBuffer);
|
||||||
/* Qmouse_face and Qkeymap are defined in textprop.c / keymap.c. */
|
/* Qmouse_face and Qkeymap are defined in textprop.c / keymap.c. */
|
||||||
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
|
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
From 0812e650c24f90bda79368078fa0ad45c18f39d2 Mon Sep 17 00:00:00 2001
|
||||||
|
From: T <t@t>
|
||||||
|
Date: Sat, 28 Feb 2026 18:45:14 +0100
|
||||||
|
Subject: [PATCH 3/3] ns: harden VoiceOver accessibility resource safety
|
||||||
|
|
||||||
|
Fix several resource safety issues found during maintainer review:
|
||||||
|
|
||||||
|
* Announcement coalescing: add 50ms minimum interval between
|
||||||
|
AnnouncementRequested notifications to prevent VoiceOver speech
|
||||||
|
synthesizer stalls from rapid-fire high-priority interruptions
|
||||||
|
(e.g. holding C-n in a completion list).
|
||||||
|
|
||||||
|
* cachedText thread safety: return [[cachedText retain] autorelease]
|
||||||
|
from accessibilityValue to prevent use-after-free when the main
|
||||||
|
thread replaces cachedText while the AX server thread is still
|
||||||
|
using the previous value.
|
||||||
|
|
||||||
|
* EmacsView dealloc safety: nil out emacsView back-references on
|
||||||
|
all accessibility elements before releasing them. Queued
|
||||||
|
dispatch_async blocks that hold a retained element reference would
|
||||||
|
otherwise access a dangling emacsView pointer.
|
||||||
|
|
||||||
|
* Nil guards: add emacsView nil checks in accessibilityParent,
|
||||||
|
accessibilityWindow, accessibilityTopLevelUIElement, and
|
||||||
|
overlayZoomActive access sites.
|
||||||
|
|
||||||
|
* src/nsterm.m (ns_ax_post_notification_with_info): Add timestamp
|
||||||
|
coalescing for AnnouncementRequested.
|
||||||
|
(accessibilityValue): Return retained+autoreleased cachedText.
|
||||||
|
(dealloc): Nil out emacsView on all accessibility elements.
|
||||||
|
(accessibilityParent, accessibilityWindow)
|
||||||
|
(accessibilityTopLevelUIElement): Add nil guards.
|
||||||
|
---
|
||||||
|
src/nsterm.m | 38 +++++++++++++++++++++++++++++++++++++-
|
||||||
|
1 file changed, 37 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||||
|
index abecb4c..3724b05 100644
|
||||||
|
--- a/src/nsterm.m
|
||||||
|
+++ b/src/nsterm.m
|
||||||
|
@@ -7521,11 +7521,32 @@ ns_ax_post_notification (id element,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
+/* Minimum interval between AnnouncementRequested notifications
|
||||||
|
+ (in seconds). VoiceOver can stall if overwhelmed with rapid-fire
|
||||||
|
+ high-priority announcements that each interrupt the previous
|
||||||
|
+ utterance. 50ms lets the speech synthesizer start before the
|
||||||
|
+ next interruption. */
|
||||||
|
+#define NS_AX_ANNOUNCE_MIN_INTERVAL 0.05
|
||||||
|
+
|
||||||
|
static inline void
|
||||||
|
ns_ax_post_notification_with_info (id element,
|
||||||
|
NSAccessibilityNotificationName name,
|
||||||
|
NSDictionary *info)
|
||||||
|
{
|
||||||
|
+ /* Coalesce AnnouncementRequested: skip if the previous one was
|
||||||
|
+ less than NS_AX_ANNOUNCE_MIN_INTERVAL seconds ago. Prevents
|
||||||
|
+ speech synthesizer stalls from rapid-fire high-priority
|
||||||
|
+ interruptions (e.g. holding C-n in a completion list). */
|
||||||
|
+ if ([name isEqualToString:
|
||||||
|
+ NSAccessibilityAnnouncementRequestedNotification])
|
||||||
|
+ {
|
||||||
|
+ static CFAbsoluteTime lastAnnouncementTime;
|
||||||
|
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent ();
|
||||||
|
+ if (now - lastAnnouncementTime < NS_AX_ANNOUNCE_MIN_INTERVAL)
|
||||||
|
+ return;
|
||||||
|
+ lastAnnouncementTime = now;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
dispatch_async (dispatch_get_main_queue (), ^{
|
||||||
|
NSAccessibilityPostNotificationWithUserInfo (element, name, info);
|
||||||
|
});
|
||||||
|
@@ -7571,16 +7592,22 @@ ns_ax_post_notification_with_info (id element,
|
||||||
|
|
||||||
|
- (id)accessibilityParent
|
||||||
|
{
|
||||||
|
+ if (!self.emacsView)
|
||||||
|
+ return nil;
|
||||||
|
return NSAccessibilityUnignoredAncestor (self.emacsView);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)accessibilityWindow
|
||||||
|
{
|
||||||
|
+ if (!self.emacsView)
|
||||||
|
+ return nil;
|
||||||
|
return [self.emacsView window];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)accessibilityTopLevelUIElement
|
||||||
|
{
|
||||||
|
+ if (!self.emacsView)
|
||||||
|
+ return nil;
|
||||||
|
return [self.emacsView window];
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -8143,7 +8170,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
[self ensureTextCache];
|
||||||
|
- return cachedText ? cachedText : @"";
|
||||||
|
+ return cachedText ? [[cachedText retain] autorelease] : @"";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)accessibilityNumberOfCharacters
|
||||||
|
@@ -9659,6 +9686,15 @@ ns_ax_scan_interactive_spans (struct window *w,
|
||||||
|
[layer release];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+ /* Nil out back-references before releasing elements. Queued
|
||||||
|
+ dispatch_async blocks may still hold a retained reference to
|
||||||
|
+ an element; without this they would access a dangling
|
||||||
|
+ emacsView pointer after EmacsView is freed. */
|
||||||
|
+ for (id elem in accessibilityElements)
|
||||||
|
+ {
|
||||||
|
+ if ([elem respondsToSelector:@selector (setEmacsView:)])
|
||||||
|
+ [elem setEmacsView:nil];
|
||||||
|
+ }
|
||||||
|
[accessibilityElements release];
|
||||||
|
[[self menu] release];
|
||||||
|
[super dealloc];
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
Reference in New Issue
Block a user