commit 5ee6854ffbe6ed627a3684fbe8d3481bd6081b4b
parent 175e4ab73d6ac0fd05030107546e969af208071d
Author: James Teh <jteh@mozilla.com>
Date: Tue, 16 Dec 2025 22:44:23 +0000
Bug 1994032 part 1: Implement UIA AccessibleActions property. r=eeejay
This is a custom property that isn't defined by Windows, so this patch also adds infrastructure to allow for registration, exposure and testing of custom properties.
Differential Revision: https://phabricator.services.mozilla.com/D276599
Diffstat:
4 files changed, 102 insertions(+), 1 deletion(-)
diff --git a/accessible/tests/browser/windows/a11y_setup.py b/accessible/tests/browser/windows/a11y_setup.py
@@ -15,7 +15,7 @@ from dataclasses import dataclass
import comtypes.automation
import comtypes.client
import psutil
-from comtypes import COMError, IServiceProvider
+from comtypes import GUID, COMError, IServiceProvider
CHILDID_SELF = 0
COWAIT_DEFAULT = 0
@@ -100,6 +100,24 @@ uiaClient = comtypes.CoCreateInstance(
clsctx=comtypes.CLSCTX_INPROC_SERVER,
)
+# Register UIA custom properties.
+# IUIAutomationRegistrar is in a different type library.
+uiaCoreMod = comtypes.client.GetModule(("{930299ce-9965-4dec-b0f4-a54848d4b667}",))
+uiaReg = comtypes.CoCreateInstance(
+ uiaCoreMod.CUIAutomationRegistrar._reg_clsid_,
+ interface=uiaCoreMod.IUIAutomationRegistrar,
+)
+uiaAccessibleActionsPropertyId = uiaReg.RegisterProperty(
+ byref(
+ uiaCoreMod.UIAutomationPropertyInfo(
+ GUID("{8C787AC3-0405-4C94-AC09-7A56A173F7EF}"),
+ "AccessibleActions",
+ uiaCoreMod.UIAutomationType_ElementArray,
+ )
+ )
+)
+del uiaReg, uiaCoreMod
+
_threadLocal = threading.local()
diff --git a/accessible/tests/browser/windows/uia/browser_relationProps.js b/accessible/tests/browser/windows/uia/browser_relationProps.js
@@ -12,6 +12,18 @@ function testUiaRelationArray(id, prop, targets) {
);
}
+function testCustomUiaRelationArray(id, prop, targets) {
+ return isUiaElementArray(
+ `
+ findUiaByDomId(doc, "${id}")
+ .GetCurrentPropertyValue(uia${prop}PropertyId)
+ .QueryInterface(IUIAutomationElementArray)
+ `,
+ targets,
+ `${id} has correct ${prop} targets`
+ );
+}
+
/**
* Test the ControllerFor property.
*/
@@ -141,3 +153,24 @@ addUiaTask(
// The IA2 -> UIA proxy doesn't expose LabeledBy properly.
{ uiaEnabled: true, uiaDisabled: false }
);
+
+/**
+ * Test the AccessibleActions property.
+ */
+addUiaTask(
+ `
+<dialog aria-actions="btn" id="dlg" onclick="" open>
+ Dialog with its own click listener
+ <form method="dialog">
+ <button id="btn">Close</button>
+ </form>
+</dialog>
+ `,
+ async function testActions() {
+ await definePyVar("doc", `getDocUia()`);
+ await testCustomUiaRelationArray("dlg", "AccessibleActions", ["btn"]);
+ await testCustomUiaRelationArray("btn", "AccessibleActions", []);
+ },
+ // The IA2 -> UIA proxy doesn't support AccessibleActions.
+ { uiaEnabled: true, uiaDisabled: false }
+);
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -775,6 +775,17 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
aPropertyValue->vt = VT_I4;
aPropertyValue->lVal = acc->GroupPosition().setSize;
return S_OK;
+
+ default: {
+ // These can't be included as case statements because they are not
+ // constant expressions.
+ const UiaRegistrations& registrations = GetUiaRegistrations();
+ if (aPropertyId == registrations.mAccessibleActions) {
+ aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY;
+ aPropertyValue->parray = AccRelationsToUiaArray({RelationType::ACTION});
+ return S_OK;
+ }
+ }
}
return S_OK;
@@ -1570,3 +1581,29 @@ SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) {
}
return uias;
}
+
+const UiaRegistrations& a11y::GetUiaRegistrations() {
+ static UiaRegistrations sRegistrations = {};
+ static bool sRegistered = false;
+ if (sRegistered) {
+ return sRegistrations;
+ }
+ RefPtr<IUIAutomationRegistrar> registrar;
+ if (FAILED(CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar,
+ getter_AddRefs(registrar)))) {
+ return sRegistrations;
+ }
+ UIAutomationPropertyInfo actionsInfo = {
+ // https://w3c.github.io/core-aam/#ariaActions
+ // {8C787AC3-0405-4C94-AC09-7A56A173F7EF}
+ {0x8C787AC3,
+ 0x0405,
+ 0x4C94,
+ {0xAC, 0x09, 0x7A, 0x56, 0xA1, 0x73, 0xF7, 0xEF}},
+ L"AccessibleActions",
+ UIAutomationType_ElementArray};
+ registrar->RegisterProperty(&actionsInfo, &sRegistrations.mAccessibleActions);
+ sRegistered = true;
+ return sRegistrations;
+}
diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h
@@ -26,6 +26,10 @@ namespace a11y {
class Accessible;
enum class RelationType;
+struct UiaRegistrations {
+ PROPERTYID mAccessibleActions = 0;
+};
+
/**
* IRawElementProviderSimple implementation (maintains IAccessibleEx approach).
*/
@@ -210,6 +214,15 @@ class uiaRawElmProvider : public IAccessibleEx,
SAFEARRAY* AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs);
+/**
+ * Get ids for custom UI Automation properties and events which are not defined
+ * by Windows and therefore need to be registered at runtime. This function will
+ * register the properties and events the first time it is called. Thereafter,
+ * cached ids will be returned, since they are valid for the lifetime of the
+ * process.
+ */
+const UiaRegistrations& GetUiaRegistrations();
+
} // namespace a11y
} // namespace mozilla