commit ccd1be9f2f7645e5278dec84fed833540f4a5098
parent 5ca4127406be3fba31681c808e4dce8320d792a3
Author: Eitan Isaacson <eitan@monotonous.org>
Date: Fri, 17 Oct 2025 05:30:07 +0000
Bug 1994030 - P1: Emulate AXUIElement API for accessibilityCustomActions. r=morgan
This `accessibilityCustomActions` method in the NSAccessibility protocol does not have a public client-side API.
Instead, it extends the existing action query and perform methods:
* `AXUIElementCopyActionNames` appends the custom actions to the action names list with a non-human readable string, something like: "Name:foo Target:0x1788d7ac0 Selector:doAction".
* `AXUIElementCopyActionDescription`, if provided with the non-human readable name above will return the `NSAccessibilityCustomAction` `name`.
* `AXUIElementPerformAction`, if provided with the non-human readable name above will execute the action (eg. perform given selector on given target).
This patch matches this behavior and adds `getActionDescription` for completeness.
Differential Revision: https://phabricator.services.mozilla.com/D268471
Diffstat:
2 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/accessible/interfaces/nsIAccessibleMacInterface.idl b/accessible/interfaces/nsIAccessibleMacInterface.idl
@@ -58,8 +58,14 @@ interface nsIAccessibleMacInterface : nsISupports
jsval getParameterizedAttributeValue(in AString attributeName, in jsval parameter);
/**
+ * Gets a description of the specified action.
+ * Emulates `AXUIElementCopyActionDescription`.
+ */
+ AString getActionDescription(in AString actionName);
+
+ /**
* Requests that the accessibility object perform the specified action.
- * Emulatets `AXUIElementPerformAction`.
+ * Emulates `AXUIElementPerformAction`.
*/
void performAction(in AString actionName);
diff --git a/accessible/xpcom/xpcAccessibleMacInterface.mm b/accessible/xpcom/xpcAccessibleMacInterface.mm
@@ -90,6 +90,13 @@ xpcAccessibleMacInterface::GetParameterizedAttributeNames(
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
}
+// Return a string that uniquely identifies a custom action.
+static NSString* GetCustomActionName(NSAccessibilityCustomAction* action) {
+ return [NSString stringWithFormat:@"Name:%@ Target:%@ Selector:%@",
+ [action name], [action target],
+ NSStringFromSelector([action selector])];
+}
+
NS_IMETHODIMP
xpcAccessibleMacInterface::GetActionNames(nsTArray<nsString>& aActionNames) {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN
@@ -104,12 +111,51 @@ xpcAccessibleMacInterface::GetActionNames(nsTArray<nsString>& aActionNames) {
aActionNames.AppendElement(actionName);
}
+ if (NSArray* customActions = [mNativeObject accessibilityCustomActions]) {
+ for (id action in customActions) {
+ nsAutoString actionName;
+ NSString* actionNameStr = GetCustomActionName(action);
+ nsCocoaUtils::GetStringForNSString(actionNameStr, actionName);
+ aActionNames.AppendElement(actionName);
+ }
+ }
+
return NS_OK;
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
}
NS_IMETHODIMP
+xpcAccessibleMacInterface::GetActionDescription(const nsAString& aActionName,
+ nsAString& aDescription) {
+ aDescription.Truncate();
+
+ if (!mNativeObject || [mNativeObject isExpired]) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NSString* actionName = nsCocoaUtils::ToNSString(aActionName);
+
+ // First search custom actions, since `accessibilityActionDescription` will
+ // just return the provided name if no description is found.
+ if (NSArray* customActions = [mNativeObject accessibilityCustomActions]) {
+ for (id action in customActions) {
+ NSString* actionNameStr = GetCustomActionName(action);
+ if ([actionNameStr isEqualToString:actionName]) {
+ nsCocoaUtils::GetStringForNSString([action name], aDescription);
+ return NS_OK;
+ }
+ }
+ }
+
+ NSString* description =
+ [mNativeObject accessibilityActionDescription:actionName];
+ nsCocoaUtils::GetStringForNSString(description, aDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
xpcAccessibleMacInterface::PerformAction(const nsAString& aActionName) {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN
@@ -118,6 +164,19 @@ xpcAccessibleMacInterface::PerformAction(const nsAString& aActionName) {
}
NSString* actionName = nsCocoaUtils::ToNSString(aActionName);
+
+ // First search custom actions, since `accessibilityPerformAction` will
+ // silently fail on unknown action names.
+ if (NSArray* customActions = [mNativeObject accessibilityCustomActions]) {
+ for (id action in customActions) {
+ NSString* actionNameStr = GetCustomActionName(action);
+ if ([actionNameStr isEqualToString:actionName]) {
+ [[action target] performSelector:[action selector]];
+ return NS_OK;
+ }
+ }
+ }
+
[mNativeObject accessibilityPerformAction:actionName];
return NS_OK;