patches: fix VoiceOver deadlock — async AX notification posting
NSAccessibilityPostNotification may synchronously invoke VoiceOver callbacks from a background AX server thread. Those callbacks call dispatch_sync(main_queue) to read buffer state. If the main thread is still inside the notification-posting method (postAccessibilityUpdates, windowDidBecomeKey, or postAccessibilityNotificationsForFrame), the dispatch_sync deadlocks. Symptom: Emacs hangs on C-x o after M-x list-buffers from Completions buffer, but only with VoiceOver enabled. Fix: introduce ns_ax_post_notification() and ns_ax_post_notification_with_info() wrappers that defer notification posting via dispatch_async(main_queue). This lets the current method return and frees the main queue for VoiceOver's dispatch_sync calls. All 14 notification-posting sites now use the async wrappers.
This commit is contained in:
@@ -310,7 +310,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
ns_focus (f, NULL, 0);
|
ns_focus (f, NULL, 0);
|
||||||
|
|
||||||
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
||||||
@@ -6849,218 +6891,2387 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
@@ -6849,218 +6891,2414 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
|
|
||||||
@@ -939,6 +939,33 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ return @"";
|
+ return @"";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+/* Post AX notifications asynchronously to prevent deadlock.
|
||||||
|
+ NSAccessibilityPostNotification may synchronously invoke VoiceOver
|
||||||
|
+ callbacks that dispatch_sync back to the main queue. If we are
|
||||||
|
+ already on the main queue (e.g., inside postAccessibilityUpdates
|
||||||
|
+ called from ns_update_end), that dispatch_sync deadlocks.
|
||||||
|
+ Deferring via dispatch_async lets the current method return first,
|
||||||
|
+ freeing the main queue for VoiceOver's dispatch_sync calls. */
|
||||||
|
+
|
||||||
|
+static inline void
|
||||||
|
+ns_ax_post_notification (id element,
|
||||||
|
+ NSAccessibilityNotificationName name)
|
||||||
|
+{
|
||||||
|
+ dispatch_async (dispatch_get_main_queue (), ^{
|
||||||
|
+ NSAccessibilityPostNotification (element, name);
|
||||||
|
+ });
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static inline void
|
||||||
|
+ns_ax_post_notification_with_info (id element,
|
||||||
|
+ NSAccessibilityNotificationName name,
|
||||||
|
+ NSDictionary *info)
|
||||||
|
+{
|
||||||
|
+ dispatch_async (dispatch_get_main_queue (), ^{
|
||||||
|
+ NSAccessibilityPostNotificationWithUserInfo (element, name, info);
|
||||||
|
+ });
|
||||||
|
+}
|
||||||
|
+
|
||||||
+/* Scan visible range of window W for interactive spans.
|
+/* Scan visible range of window W for interactive spans.
|
||||||
+ Returns NSArray<EmacsAccessibilityInteractiveSpan *>.
|
+ Returns NSArray<EmacsAccessibilityInteractiveSpan *>.
|
||||||
|
|
||||||
@@ -1758,7 +1785,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ @(ns_ax_text_state_change_selection_move),
|
+ @(ns_ax_text_state_change_selection_move),
|
||||||
+ @"AXTextChangeElement": self
|
+ @"AXTextChangeElement": self
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
+ ns_ax_post_notification_with_info (
|
||||||
+ self, NSAccessibilitySelectedTextChangedNotification, info);
|
+ self, NSAccessibilitySelectedTextChangedNotification, info);
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
@@ -2135,7 +2162,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ @"AXTextChangeValues": @[change],
|
+ @"AXTextChangeValues": @[change],
|
||||||
+ @"AXTextChangeElement": self
|
+ @"AXTextChangeElement": self
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
+ ns_ax_post_notification_with_info (
|
||||||
+ self, NSAccessibilityValueChangedNotification, userInfo);
|
+ self, NSAccessibilityValueChangedNotification, userInfo);
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
@@ -2244,7 +2271,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ if (!isCharMove)
|
+ if (!isCharMove)
|
||||||
+ moveInfo[@"AXTextSelectionGranularity"] = @(granularity);
|
+ moveInfo[@"AXTextSelectionGranularity"] = @(granularity);
|
||||||
+
|
+
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
+ ns_ax_post_notification_with_info (
|
||||||
+ self,
|
+ self,
|
||||||
+ NSAccessibilitySelectedTextChangedNotification,
|
+ NSAccessibilitySelectedTextChangedNotification,
|
||||||
+ moveInfo);
|
+ moveInfo);
|
||||||
@@ -2275,7 +2302,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ NSAccessibilityPriorityKey:
|
+ NSAccessibilityPriorityKey:
|
||||||
+ @(NSAccessibilityPriorityHigh)
|
+ @(NSAccessibilityPriorityHigh)
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
+ ns_ax_post_notification_with_info (
|
||||||
+ NSApp,
|
+ NSApp,
|
||||||
+ NSAccessibilityAnnouncementRequestedNotification,
|
+ NSAccessibilityAnnouncementRequestedNotification,
|
||||||
+ annInfo);
|
+ annInfo);
|
||||||
@@ -2332,7 +2359,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ NSAccessibilityPriorityKey:
|
+ NSAccessibilityPriorityKey:
|
||||||
+ @(NSAccessibilityPriorityHigh)
|
+ @(NSAccessibilityPriorityHigh)
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
+ ns_ax_post_notification_with_info (
|
||||||
+ NSApp,
|
+ NSApp,
|
||||||
+ NSAccessibilityAnnouncementRequestedNotification,
|
+ NSAccessibilityAnnouncementRequestedNotification,
|
||||||
+ annInfo);
|
+ annInfo);
|
||||||
@@ -2495,7 +2522,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ NSAccessibilityPriorityKey:
|
+ NSAccessibilityPriorityKey:
|
||||||
+ @(NSAccessibilityPriorityHigh)
|
+ @(NSAccessibilityPriorityHigh)
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
+ ns_ax_post_notification_with_info (
|
||||||
+ NSApp,
|
+ NSApp,
|
||||||
+ NSAccessibilityAnnouncementRequestedNotification,
|
+ NSAccessibilityAnnouncementRequestedNotification,
|
||||||
+ annInfo);
|
+ annInfo);
|
||||||
@@ -2846,7 +2873,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
int code;
|
int code;
|
||||||
unsigned fnKeysym = 0;
|
unsigned fnKeysym = 0;
|
||||||
static NSMutableArray *nsEvArray;
|
static NSMutableArray *nsEvArray;
|
||||||
@@ -8237,6 +10448,31 @@ - (void)windowDidBecomeKey /* for direct calls */
|
@@ -8237,6 +10475,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
|
||||||
@@ -2860,25 +2887,25 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ if (focused
|
+ if (focused
|
||||||
+ && [focused isKindOfClass:[EmacsAccessibilityBuffer class]])
|
+ && [focused isKindOfClass:[EmacsAccessibilityBuffer class]])
|
||||||
+ {
|
+ {
|
||||||
+ NSAccessibilityPostNotification (focused,
|
+ ns_ax_post_notification (focused,
|
||||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
+ NSDictionary *info = @{
|
+ NSDictionary *info = @{
|
||||||
+ @"AXTextStateChangeType":
|
+ @"AXTextStateChangeType":
|
||||||
+ @(ns_ax_text_state_change_selection_move),
|
+ @(ns_ax_text_state_change_selection_move),
|
||||||
+ @"AXTextChangeElement": focused
|
+ @"AXTextChangeElement": focused
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (focused,
|
+ ns_ax_post_notification_with_info (focused,
|
||||||
+ NSAccessibilitySelectedTextChangedNotification, info);
|
+ NSAccessibilitySelectedTextChangedNotification, info);
|
||||||
+ }
|
+ }
|
||||||
+ else if (focused)
|
+ else if (focused)
|
||||||
+ NSAccessibilityPostNotification (focused,
|
+ ns_ax_post_notification (focused,
|
||||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
+ }
|
+ }
|
||||||
+#endif
|
+#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9474,6 +11710,332 @@ - (int) fullscreenState
|
@@ -9474,6 +11737,332 @@ - (int) fullscreenState
|
||||||
return fs_state;
|
return fs_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3062,13 +3089,13 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ for (EmacsAccessibilityElement *elem in accessibilityElements)
|
+ for (EmacsAccessibilityElement *elem in accessibilityElements)
|
||||||
+ if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]])
|
+ if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]])
|
||||||
+ [(EmacsAccessibilityBuffer *) elem invalidateInteractiveSpans];
|
+ [(EmacsAccessibilityBuffer *) elem invalidateInteractiveSpans];
|
||||||
+ NSAccessibilityPostNotification (self,
|
+ ns_ax_post_notification (self,
|
||||||
+ NSAccessibilityLayoutChangedNotification);
|
+ NSAccessibilityLayoutChangedNotification);
|
||||||
+
|
+
|
||||||
+ /* Post focus change so VoiceOver picks up the new tree. */
|
+ /* Post focus change so VoiceOver picks up the new tree. */
|
||||||
+ id focused = [self accessibilityFocusedUIElement];
|
+ id focused = [self accessibilityFocusedUIElement];
|
||||||
+ if (focused && focused != self)
|
+ if (focused && focused != self)
|
||||||
+ NSAccessibilityPostNotification (focused,
|
+ ns_ax_post_notification (focused,
|
||||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
+
|
+
|
||||||
+ lastSelectedWindow = emacsframe->selected_window;
|
+ lastSelectedWindow = emacsframe->selected_window;
|
||||||
@@ -3101,18 +3128,18 @@ index 932d209f..ea2de6f2 100644
|
|||||||
+ if (focused && focused != self
|
+ if (focused && focused != self
|
||||||
+ && [focused isKindOfClass:[EmacsAccessibilityBuffer class]])
|
+ && [focused isKindOfClass:[EmacsAccessibilityBuffer class]])
|
||||||
+ {
|
+ {
|
||||||
+ NSAccessibilityPostNotification (focused,
|
+ ns_ax_post_notification (focused,
|
||||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
+ NSDictionary *info = @{
|
+ NSDictionary *info = @{
|
||||||
+ @"AXTextStateChangeType":
|
+ @"AXTextStateChangeType":
|
||||||
+ @(ns_ax_text_state_change_selection_move),
|
+ @(ns_ax_text_state_change_selection_move),
|
||||||
+ @"AXTextChangeElement": focused
|
+ @"AXTextChangeElement": focused
|
||||||
+ };
|
+ };
|
||||||
+ NSAccessibilityPostNotificationWithUserInfo (focused,
|
+ ns_ax_post_notification_with_info (focused,
|
||||||
+ NSAccessibilitySelectedTextChangedNotification, info);
|
+ NSAccessibilitySelectedTextChangedNotification, info);
|
||||||
+ }
|
+ }
|
||||||
+ else if (focused && focused != self)
|
+ else if (focused && focused != self)
|
||||||
+ NSAccessibilityPostNotification (focused,
|
+ ns_ax_post_notification (focused,
|
||||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
@@ -3211,7 +3238,7 @@ index 932d209f..ea2de6f2 100644
|
|||||||
@end /* EmacsView */
|
@end /* EmacsView */
|
||||||
|
|
||||||
|
|
||||||
@@ -11303,6 +13865,18 @@ Convert an X font name (XLFD) to an NS font name.
|
@@ -11303,6 +13892,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