Skip to content

Commit 33ecf56

Browse files
authored
Merge pull request #295 from mozilla/enterprise-main_merge_20251218
Enterprise main merge 20251218
2 parents 3f20dc8 + 8bf4ba3 commit 33ecf56

File tree

3,567 files changed

+259962
-31893
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3,567 files changed

+259962
-31893
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,7 @@ toolkit/crashreporter/minidump-analyzer/target/
394394

395395
# Ignore personal preferences files
396396
CLAUDE.local.md
397+
398+
# Ignore .json.gz (typically profiles) in the root directory
399+
# lint-ignore-next-line: git-only
400+
/*.json.gz

accessible/basetypes/Accessible.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,20 @@ class Accessible {
794794
*/
795795
virtual bool HasPrimaryAction() const = 0;
796796

797+
/**
798+
* Return true if this Accessible has custom actions, even if those actions
799+
* aren't currently available. Custom actions are secondary actions provided
800+
* by the author using associated elements (e.g. via aria-actions), in
801+
* contrast to actions provided by Gecko on the element itself (e.g. click).
802+
* Custom actions are queried using RelationByType(RelationType::ACTION).
803+
* However, there can be cases where there are associated custom actions, but
804+
* the target elements are hidden; e.g. because the origin element isn't
805+
* focused. The client might need to know there are actions even if it can't
806+
* currently query them. For this case, this function will return true, even
807+
* though RelationByType will return nothing.
808+
*/
809+
virtual bool HasCustomActions() const = 0;
810+
797811
protected:
798812
// Some abstracted group utility methods.
799813

accessible/generic/LocalAccessible.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
11211121
attribIter.ExposeAttr(attributes);
11221122
}
11231123

1124-
if (nsAccUtils::HasARIAAttr(Elm(), nsGkAtoms::aria_actions)) {
1124+
if (HasCustomActions()) {
11251125
attributes->SetAttribute(nsGkAtoms::hasActions, true);
11261126
}
11271127

@@ -4091,7 +4091,7 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
40914091
fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry());
40924092
}
40934093

4094-
if (nsAccUtils::HasARIAAttr(Elm(), nsGkAtoms::aria_actions)) {
4094+
if (HasCustomActions()) {
40954095
fields->SetAttribute(CacheKey::HasActions, true);
40964096
} else if (IsUpdatePush(CacheDomain::ARIA)) {
40974097
fields->SetAttribute(CacheKey::HasActions, DeleteEntry());
@@ -4566,3 +4566,8 @@ bool LocalAccessible::HasARIAAttr(nsAtom* aAttrName) const {
45664566
return mContent ? nsAccUtils::HasDefinedARIAToken(mContent, aAttrName)
45674567
: false;
45684568
}
4569+
4570+
bool LocalAccessible::HasCustomActions() const {
4571+
dom::Element* el = Elm();
4572+
return el && nsAccUtils::HasARIAAttr(el, nsGkAtoms::aria_actions);
4573+
}

accessible/generic/LocalAccessible.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ class LocalAccessible : public nsISupports, public Accessible {
496496

497497
virtual bool DoAction(uint8_t aIndex) const override;
498498

499+
virtual bool HasCustomActions() const override;
500+
499501
virtual KeyBinding AccessKey() const override;
500502

501503
/**

accessible/ipc/RemoteAccessible.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,9 +1952,8 @@ already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
19521952
attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
19531953
}
19541954

1955-
if (auto hasActions =
1956-
mCachedFields->GetAttribute<bool>(CacheKey::HasActions)) {
1957-
attributes->SetAttribute(nsGkAtoms::hasActions, *hasActions);
1955+
if (HasCustomActions()) {
1956+
attributes->SetAttribute(nsGkAtoms::hasActions, true);
19581957
}
19591958

19601959
nsString detailsFrom;
@@ -2668,6 +2667,14 @@ void RemoteAccessible::PasteText(int32_t aPosition) {
26682667
(void)mDoc->SendPasteText(mID, aPosition);
26692668
}
26702669

2670+
bool RemoteAccessible::HasCustomActions() const {
2671+
if (RequestDomainsIfInactive(CacheDomain::ARIA) || !mCachedFields) {
2672+
return false;
2673+
}
2674+
auto hasActions = mCachedFields->GetAttribute<bool>(CacheKey::HasActions);
2675+
return hasActions && *hasActions;
2676+
}
2677+
26712678
size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
26722679
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
26732680
}

accessible/ipc/RemoteAccessible.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ class RemoteAccessible : public Accessible, public HyperTextAccessibleBase {
392392

393393
virtual bool HasPrimaryAction() const override;
394394

395+
virtual bool HasCustomActions() const override;
395396
virtual bool IsEditable() const override;
396397

397398
#if !defined(XP_WIN)

accessible/tests/browser/tree/browser_searchbar.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ const { CustomizableUITestUtils } = ChromeUtils.importESModule(
88
);
99
let gCUITestUtils = new CustomizableUITestUtils(window);
1010

11+
add_setup(async function () {
12+
await SpecialPowers.pushPrefEnv({
13+
set: [["browser.search.widget.new", false]],
14+
});
15+
});
16+
1117
// eslint-disable-next-line camelcase
1218
add_task(async function test_searchbar_a11y_tree() {
1319
let searchbar = await gCUITestUtils.addSearchBar();

accessible/tests/browser/windows/a11y_setup.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import comtypes.automation
1616
import comtypes.client
1717
import psutil
18-
from comtypes import COMError, IServiceProvider
18+
from comtypes import GUID, COMError, IServiceProvider
1919

2020
CHILDID_SELF = 0
2121
COWAIT_DEFAULT = 0
@@ -100,6 +100,24 @@ def registerIa2Proxy():
100100
clsctx=comtypes.CLSCTX_INPROC_SERVER,
101101
)
102102

103+
# Register UIA custom properties.
104+
# IUIAutomationRegistrar is in a different type library.
105+
uiaCoreMod = comtypes.client.GetModule(("{930299ce-9965-4dec-b0f4-a54848d4b667}",))
106+
uiaReg = comtypes.CoCreateInstance(
107+
uiaCoreMod.CUIAutomationRegistrar._reg_clsid_,
108+
interface=uiaCoreMod.IUIAutomationRegistrar,
109+
)
110+
uiaAccessibleActionsPropertyId = uiaReg.RegisterProperty(
111+
byref(
112+
uiaCoreMod.UIAutomationPropertyInfo(
113+
GUID("{8C787AC3-0405-4C94-AC09-7A56A173F7EF}"),
114+
"AccessibleActions",
115+
uiaCoreMod.UIAutomationType_ElementArray,
116+
)
117+
)
118+
)
119+
del uiaReg, uiaCoreMod
120+
103121
_threadLocal = threading.local()
104122

105123

accessible/tests/browser/windows/uia/browser_relationProps.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ function testUiaRelationArray(id, prop, targets) {
1212
);
1313
}
1414

15+
function testCustomUiaRelationArray(id, prop, targets) {
16+
return isUiaElementArray(
17+
`
18+
findUiaByDomId(doc, "${id}")
19+
.GetCurrentPropertyValue(uia${prop}PropertyId)
20+
.QueryInterface(IUIAutomationElementArray)
21+
`,
22+
targets,
23+
`${id} has correct ${prop} targets`
24+
);
25+
}
26+
1527
/**
1628
* Test the ControllerFor property.
1729
*/
@@ -141,3 +153,65 @@ addUiaTask(
141153
// The IA2 -> UIA proxy doesn't expose LabeledBy properly.
142154
{ uiaEnabled: true, uiaDisabled: false }
143155
);
156+
157+
/**
158+
* Test the AccessibleActions property.
159+
*/
160+
addUiaTask(
161+
`
162+
<dialog aria-actions="btn" id="dlg" onclick="" open>
163+
Dialog with its own click listener
164+
<form method="dialog">
165+
<button id="btn">Close</button>
166+
</form>
167+
</dialog>
168+
`,
169+
async function testActions() {
170+
await definePyVar("doc", `getDocUia()`);
171+
await testCustomUiaRelationArray("dlg", "AccessibleActions", ["btn"]);
172+
await testCustomUiaRelationArray("btn", "AccessibleActions", []);
173+
},
174+
// The IA2 -> UIA proxy doesn't support AccessibleActions.
175+
{ uiaEnabled: true, uiaDisabled: false }
176+
);
177+
178+
/**
179+
* Test exposure of AriaProperties.hasactions.
180+
*/
181+
addUiaTask(
182+
`
183+
<button id="button">button</button>
184+
<div role="tablist">
185+
<div id="tab1" role="tab" tabindex="0" aria-actions="tab1Button">
186+
tab1
187+
<button id="tab1Button">tab1Button</button>
188+
</div>
189+
<div id="tab2" role="tab" aria-actions="tab2Button">
190+
tab2
191+
<button id="tab2Button" hidden>tab2Button</button>
192+
</div>
193+
</div>
194+
`,
195+
async function testHasActions() {
196+
await definePyVar("doc", `getDocUia()`);
197+
is(
198+
await runPython(`findUiaByDomId(doc, "button").CurrentAriaProperties`),
199+
"",
200+
"button missing hasactions"
201+
);
202+
// tab1 has a visible action.
203+
is(
204+
await runPython(`findUiaByDomId(doc, "tab1").CurrentAriaProperties`),
205+
"hasactions=true",
206+
"tab1 hasactions=true"
207+
);
208+
// tab2 has an action, but it's hidden.
209+
is(
210+
await runPython(`findUiaByDomId(doc, "tab2").CurrentAriaProperties`),
211+
"hasactions=true",
212+
"tab2 hasactions=true"
213+
);
214+
},
215+
// The IA2 -> UIA proxy doesn't support hasactions.
216+
{ uiaEnabled: true, uiaDisabled: false }
217+
);

accessible/windows/uia/uiaRawElmProvider.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,12 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
596596
ariaProperties.AppendLiteral("atomic=false");
597597
}
598598
}
599+
if (acc->HasCustomActions()) {
600+
if (!ariaProperties.IsEmpty()) {
601+
ariaProperties += ';';
602+
}
603+
ariaProperties.AppendLiteral("hasactions=true");
604+
}
599605
if (!ariaProperties.IsEmpty()) {
600606
aPropertyValue->vt = VT_BSTR;
601607
aPropertyValue->bstrVal = ::SysAllocString(ariaProperties.get());
@@ -775,6 +781,17 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
775781
aPropertyValue->vt = VT_I4;
776782
aPropertyValue->lVal = acc->GroupPosition().setSize;
777783
return S_OK;
784+
785+
default: {
786+
// These can't be included as case statements because they are not
787+
// constant expressions.
788+
const UiaRegistrations& registrations = GetUiaRegistrations();
789+
if (aPropertyId == registrations.mAccessibleActions) {
790+
aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY;
791+
aPropertyValue->parray = AccRelationsToUiaArray({RelationType::ACTION});
792+
return S_OK;
793+
}
794+
}
778795
}
779796

780797
return S_OK;
@@ -1570,3 +1587,29 @@ SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) {
15701587
}
15711588
return uias;
15721589
}
1590+
1591+
const UiaRegistrations& a11y::GetUiaRegistrations() {
1592+
static UiaRegistrations sRegistrations = {};
1593+
static bool sRegistered = false;
1594+
if (sRegistered) {
1595+
return sRegistrations;
1596+
}
1597+
RefPtr<IUIAutomationRegistrar> registrar;
1598+
if (FAILED(CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr,
1599+
CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar,
1600+
getter_AddRefs(registrar)))) {
1601+
return sRegistrations;
1602+
}
1603+
UIAutomationPropertyInfo actionsInfo = {
1604+
// https://w3c.github.io/core-aam/#ariaActions
1605+
// {8C787AC3-0405-4C94-AC09-7A56A173F7EF}
1606+
{0x8C787AC3,
1607+
0x0405,
1608+
0x4C94,
1609+
{0xAC, 0x09, 0x7A, 0x56, 0xA1, 0x73, 0xF7, 0xEF}},
1610+
L"AccessibleActions",
1611+
UIAutomationType_ElementArray};
1612+
registrar->RegisterProperty(&actionsInfo, &sRegistrations.mAccessibleActions);
1613+
sRegistered = true;
1614+
return sRegistrations;
1615+
}

0 commit comments

Comments
 (0)