tor-browser

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

commit fd81a764be34cd0a4b9720f33104e9c24eafda97
parent 8641808763bed9ae74a03772f1b21f8c654ddbe8
Author: Eitan Isaacson <eitan@monotonous.org>
Date:   Wed, 22 Oct 2025 03:44:58 +0000

Bug 1994031 - P2: Support aria-actions IA2. r=Jamie

Differential Revision: https://phabricator.services.mozilla.com/D268736

Diffstat:
Maccessible/tests/browser/windows/ia2/browser_action.js | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Maccessible/windows/ia2/ia2AccessibleAction.cpp | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 133 insertions(+), 3 deletions(-)

diff --git a/accessible/tests/browser/windows/ia2/browser_action.js b/accessible/tests/browser/windows/ia2/browser_action.js @@ -28,3 +28,63 @@ addAccessibleTask( await nameChanged; } ); + +/** + * Test aria-actions. + */ +addAccessibleTask( + ` + <div id="container"> + <dialog aria-actions="btn1" id="dlg1" open> + Hello + <form method="dialog"> + <button id="btn1">Close</button> + </form> + </dialog> + <dialog aria-actions="btn2" id="dlg2" onclick="" open> + Dialog with its own click listener + <form method="dialog"> + <button id="btn2">Close</button> + </form> + </dialog> + </div>`, + async function testAriaActions() { + let actions = await runPython(` + global doc + doc = getDocIa2() + global dlg1 + dlg1 = findIa2ByDomId(doc, "dlg1").QueryInterface(IAccessibleAction) + return str([[dlg1.name(i), dlg1.localizedName(i), dlg1.description(i)] for i in range(dlg1.nActions())]) + `); + is( + actions, + "[['custom_btn1', 'Close', 'Close']]", + "dlg1 has correct actions" + ); + + let reorder = waitForEvent(EVENT_REORDER, "container"); + await runPython(` + dlg1.doAction(0) + `); + await reorder; + + // Test dialog with its own click listener, and therefore has the aria-actions + // target actions appended to its own actions. + actions = await runPython(` + global dlg2 + dlg2 = findIa2ByDomId(doc, "dlg2").QueryInterface(IAccessibleAction) + return str([[dlg2.name(i), dlg2.localizedName(i), dlg2.description(i)] for i in range(dlg2.nActions())]) + `); + is( + actions, + "[['click', 'Click', 'Click'], ['custom_btn2', 'Close', 'Close']]", + "dlg2 has correct actions" + ); + + reorder = waitForEvent(EVENT_REORDER, "container"); + await runPython(` + dlg2.doAction(1) + `); + await reorder; + } +); diff --git a/accessible/windows/ia2/ia2AccessibleAction.cpp b/accessible/windows/ia2/ia2AccessibleAction.cpp @@ -12,6 +12,7 @@ #include "AccessibleWrap.h" #include "IUnknownImpl.h" #include "MsaaAccessible.h" +#include "Relation.h" using namespace mozilla::a11y; @@ -48,6 +49,13 @@ ia2AccessibleAction::nActions(long* aActionCount) { if (!acc) return CO_E_OBJNOTCONNECTED; *aActionCount = acc->ActionCount(); + Relation customActions(acc->RelationByType(RelationType::ACTION)); + while (Accessible* target = customActions.Next()) { + if (target->HasPrimaryAction()) { + (*aActionCount)++; + } + } + return S_OK; } @@ -57,7 +65,29 @@ ia2AccessibleAction::doAction(long aActionIndex) { if (!acc) return CO_E_OBJNOTCONNECTED; uint8_t index = static_cast<uint8_t>(aActionIndex); - return acc->DoAction(index) ? S_OK : E_INVALIDARG; + + if (index < acc->ActionCount()) { + DebugOnly<bool> success = acc->DoAction(aActionIndex); + MOZ_ASSERT(success, "Failed to perform action"); + return S_OK; + } + + // Check for custom actions. + Relation customActions(acc->RelationByType(RelationType::ACTION)); + uint8_t actionIndex = acc->ActionCount(); + while (Accessible* target = customActions.Next()) { + if (target->HasPrimaryAction()) { + MOZ_ASSERT(target->ActionCount() > 0); + if (actionIndex == index) { + DebugOnly<bool> success = target->DoAction(0); + MOZ_ASSERT(success, "Failed to perform action"); + return S_OK; + } + actionIndex++; + } + } + + return E_INVALIDARG; } STDMETHODIMP @@ -124,7 +154,29 @@ ia2AccessibleAction::get_name(long aActionIndex, BSTR* aName) { nsAutoString name; uint8_t index = static_cast<uint8_t>(aActionIndex); - acc->ActionNameAt(index, name); + if (index < acc->ActionCount()) { + acc->ActionNameAt(aActionIndex, name); + } else { + // Check for custom actions. + Relation customActions(acc->RelationByType(RelationType::ACTION)); + uint8_t actionIndex = acc->ActionCount(); + while (Accessible* target = customActions.Next()) { + if (target->HasPrimaryAction()) { + MOZ_ASSERT(target->ActionCount() > 0); + if (actionIndex == index) { + name.AssignLiteral("custom"); + nsAutoString domNodeId; + target->DOMNodeID(domNodeId); + if (!domNodeId.IsEmpty()) { + name.AppendPrintf("_%s", NS_ConvertUTF16toUTF8(domNodeId).get()); + } + break; + } + actionIndex++; + } + } + } + if (name.IsEmpty()) return E_INVALIDARG; *aName = ::SysAllocStringLen(name.get(), name.Length()); @@ -142,7 +194,25 @@ ia2AccessibleAction::get_localizedName(long aActionIndex, nsAutoString description; uint8_t index = static_cast<uint8_t>(aActionIndex); - acc->ActionDescriptionAt(index, description); + + if (aActionIndex < acc->ActionCount()) { + acc->ActionDescriptionAt(index, description); + } else { + // Check for custom actions. + Relation customActions(acc->RelationByType(RelationType::ACTION)); + uint8_t actionIndex = acc->ActionCount(); + while (Accessible* target = customActions.Next()) { + if (target->HasPrimaryAction()) { + MOZ_ASSERT(target->ActionCount() > 0); + if (actionIndex == index) { + target->Name(description); + break; + } + actionIndex++; + } + } + } + if (description.IsEmpty()) return S_FALSE; *aLocalizedName =