mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1887800 part 2: Support the UIA LabeledBy property. r=morgan
Differential Revision: https://phabricator.services.mozilla.com/D208852
This commit is contained in:
parent
29645bd082
commit
d48615b61b
@ -91,3 +91,53 @@ addUiaTask(
|
||||
await testUiaRelationArray("none", "FlowsTo", []);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Test the LabeledBy property.
|
||||
*/
|
||||
addUiaTask(
|
||||
`
|
||||
<label id="label">label</label>
|
||||
<input id="input" aria-labelledby="label">
|
||||
<label id="wrappingLabel">
|
||||
<input id="wrappedInput" value="wrappedInput">
|
||||
<p id="wrappingLabelP">wrappingLabel</p>
|
||||
</label>
|
||||
<button id="button" aria-labelledby="label">content</button>
|
||||
<button id="noLabel">noLabel</button>
|
||||
`,
|
||||
async function testLabeledBy() {
|
||||
await definePyVar("doc", `getDocUia()`);
|
||||
// input's LabeledBy should be label's text leaf.
|
||||
let result = await runPython(`
|
||||
input = findUiaByDomId(doc, "input")
|
||||
label = findUiaByDomId(doc, "label")
|
||||
labelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(label)
|
||||
return uiaClient.CompareElements(input.CurrentLabeledBy, labelLeaf)
|
||||
`);
|
||||
ok(result, "input has correct LabeledBy");
|
||||
// wrappedInput's LabeledBy should be wrappingLabelP's text leaf.
|
||||
result = await runPython(`
|
||||
wrappedInput = findUiaByDomId(doc, "wrappedInput")
|
||||
wrappingLabelP = findUiaByDomId(doc, "wrappingLabelP")
|
||||
wrappingLabelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(wrappingLabelP)
|
||||
return uiaClient.CompareElements(wrappedInput.CurrentLabeledBy, wrappingLabelLeaf)
|
||||
`);
|
||||
ok(result, "wrappedInput has correct LabeledBy");
|
||||
// button has aria-labelledby, but UIA prohibits LabeledBy on buttons.
|
||||
ok(
|
||||
!(await runPython(
|
||||
`bool(findUiaByDomId(doc, "button").CurrentLabeledBy)`
|
||||
)),
|
||||
"button has no LabeledBy"
|
||||
);
|
||||
ok(
|
||||
!(await runPython(
|
||||
`bool(findUiaByDomId(doc, "noLabel").CurrentLabeledBy)`
|
||||
)),
|
||||
"noLabel has no LabeledBy"
|
||||
);
|
||||
},
|
||||
// The IA2 -> UIA proxy doesn't expose LabeledBy properly.
|
||||
{ uiaEnabled: true, uiaDisabled: false }
|
||||
);
|
||||
|
@ -22,7 +22,9 @@
|
||||
#include "MsaaRootAccessible.h"
|
||||
#include "nsAccessibilityService.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "nsIAccessiblePivot.h"
|
||||
#include "nsTextEquivUtils.h"
|
||||
#include "Pivot.h"
|
||||
#include "Relation.h"
|
||||
#include "RootAccessible.h"
|
||||
|
||||
@ -60,6 +62,29 @@ static bool IsRadio(Accessible* aAcc) {
|
||||
return r == roles::RADIOBUTTON || r == roles::RADIO_MENU_ITEM;
|
||||
}
|
||||
|
||||
// Used to search for a text leaf descendant for the LabeledBy property.
|
||||
class LabelTextLeafRule : public PivotRule {
|
||||
public:
|
||||
virtual uint16_t Match(Accessible* aAcc) override {
|
||||
if (aAcc->IsTextLeaf()) {
|
||||
nsAutoString name;
|
||||
aAcc->Name(name);
|
||||
if (name.IsEmpty() || name.EqualsLiteral(" ")) {
|
||||
// An empty or white space text leaf isn't useful as a label.
|
||||
return nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
}
|
||||
return nsIAccessibleTraversalRule::FILTER_MATCH;
|
||||
}
|
||||
if (!nsTextEquivUtils::HasNameRule(aAcc, eNameFromSubtreeIfReqRule)) {
|
||||
// Don't descend into things that can't be used as label content; e.g.
|
||||
// text boxes.
|
||||
return nsIAccessibleTraversalRule::FILTER_IGNORE |
|
||||
nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
|
||||
}
|
||||
return nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// uiaRawElmProvider
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -602,6 +627,15 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
|
||||
(acc->State() & states::FOCUSABLE) ? VARIANT_TRUE : VARIANT_FALSE;
|
||||
return S_OK;
|
||||
|
||||
case UIA_LabeledByPropertyId:
|
||||
if (Accessible* target = GetLabeledBy()) {
|
||||
aPropertyValue->vt = VT_UNKNOWN;
|
||||
RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(target);
|
||||
uia.forget(&aPropertyValue->punkVal);
|
||||
return S_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case UIA_LevelPropertyId:
|
||||
aPropertyValue->vt = VT_I4;
|
||||
aPropertyValue->lVal = acc->GroupPosition().level;
|
||||
@ -1262,6 +1296,46 @@ SAFEARRAY* uiaRawElmProvider::AccRelationsToUiaArray(
|
||||
return AccessibleArrayToUiaArray(targets);
|
||||
}
|
||||
|
||||
Accessible* uiaRawElmProvider::GetLabeledBy() const {
|
||||
// Per the UIA documentation, some control types should never get a value for
|
||||
// the LabeledBy property.
|
||||
switch (GetControlType()) {
|
||||
case UIA_ButtonControlTypeId:
|
||||
case UIA_CheckBoxControlTypeId:
|
||||
case UIA_DataItemControlTypeId:
|
||||
case UIA_MenuControlTypeId:
|
||||
case UIA_MenuBarControlTypeId:
|
||||
case UIA_RadioButtonControlTypeId:
|
||||
case UIA_ScrollBarControlTypeId:
|
||||
case UIA_SeparatorControlTypeId:
|
||||
case UIA_StatusBarControlTypeId:
|
||||
case UIA_TabItemControlTypeId:
|
||||
case UIA_TextControlTypeId:
|
||||
case UIA_ToolBarControlTypeId:
|
||||
case UIA_ToolTipControlTypeId:
|
||||
case UIA_TreeItemControlTypeId:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Accessible* acc = Acc();
|
||||
MOZ_ASSERT(acc);
|
||||
// Even when LabeledBy is supported, it can only return a single "static text"
|
||||
// element.
|
||||
Relation rel = acc->RelationByType(RelationType::LABELLED_BY);
|
||||
LabelTextLeafRule rule;
|
||||
while (Accessible* target = rel.Next()) {
|
||||
// If target were a text leaf, we should return that, but that shouldn't be
|
||||
// possible because only an element (not a text node) can be the target of a
|
||||
// relation.
|
||||
MOZ_ASSERT(!target->IsTextLeaf());
|
||||
Pivot pivot(target);
|
||||
if (Accessible* leaf = pivot.Next(target, rule)) {
|
||||
return leaf;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) {
|
||||
if (aAccs.IsEmpty()) {
|
||||
// The UIA documentation is unclear about this, but the UIA client
|
||||
|
@ -191,6 +191,7 @@ class uiaRawElmProvider : public IAccessibleEx,
|
||||
bool HasSelectionItemPattern();
|
||||
SAFEARRAY* AccRelationsToUiaArray(
|
||||
std::initializer_list<RelationType> aTypes) const;
|
||||
Accessible* GetLabeledBy() const;
|
||||
};
|
||||
|
||||
SAFEARRAY* AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs);
|
||||
|
Loading…
Reference in New Issue
Block a user