tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 632fdf0841deb7b9407d91d4a2c05f0bdbeb7950
parent 93dfc5ab873d5a4b28cdd9dbfc7ca239426d2477
Author: Eitan Isaacson <eitan@monotonous.org>
Date:   Wed, 15 Oct 2025 17:15:11 +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:
Maccessible/interfaces/nsIAccessibleMacInterface.idl | 8+++++++-
Maccessible/xpcom/xpcAccessibleMacInterface.mm | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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;