Bug 1912520 - Morph aria-owns to controls relationship when container is combobox. r=Jamie
Differential Revision: https://phabricator.services.mozilla.com/D236931
This commit is contained in:
@@ -637,3 +637,13 @@ int32_t nsAccUtils::FindARIAAttrValueIn(dom::Element* aElement,
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
bool nsAccUtils::IsEditableARIACombobox(const LocalAccessible* aAccessible) {
|
||||
const nsRoleMapEntry* roleMap = aAccessible->ARIARoleMap();
|
||||
if (!roleMap || roleMap->role != roles::EDITCOMBOBOX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return aAccessible->IsTextField() ||
|
||||
aAccessible->Elm()->State().HasState(dom::ElementState::READWRITE);
|
||||
}
|
||||
|
||||
@@ -301,6 +301,8 @@ class nsAccUtils {
|
||||
const nsAtom* aName,
|
||||
AttrArray::AttrValuesArray* aValues,
|
||||
nsCaseTreatment aCaseSensitive);
|
||||
|
||||
static bool IsEditableARIACombobox(const LocalAccessible* aAccessible);
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
||||
@@ -2470,6 +2470,13 @@ void DocAccessible::DoARIAOwnsRelocation(LocalAccessible* aOwner) {
|
||||
logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
|
||||
#endif
|
||||
|
||||
const nsRoleMapEntry* roleMap = aOwner->ARIARoleMap();
|
||||
if (roleMap && roleMap->role == roles::EDITCOMBOBOX) {
|
||||
// The READWRITE state of a combobox may sever aria-owns relations
|
||||
// we fallback to "controls" relations.
|
||||
QueueCacheUpdate(aOwner, CacheDomain::Relations);
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<LocalAccessible>>* owned =
|
||||
mARIAOwnsHash.GetOrInsertNew(aOwner);
|
||||
|
||||
|
||||
@@ -2281,14 +2281,32 @@ Relation LocalAccessible::RelationByType(RelationType aType) const {
|
||||
return Relation();
|
||||
}
|
||||
|
||||
case RelationType::CONTROLLED_BY:
|
||||
return Relation(new RelatedAccIterator(Document(), mContent,
|
||||
nsGkAtoms::aria_controls));
|
||||
case RelationType::CONTROLLED_BY: {
|
||||
Relation rel(new RelatedAccIterator(Document(), mContent,
|
||||
nsGkAtoms::aria_controls));
|
||||
|
||||
RelatedAccIterator owners(Document(), mContent, nsGkAtoms::aria_owns);
|
||||
if (LocalAccessible* owner = owners.Next()) {
|
||||
if (nsAccUtils::IsEditableARIACombobox(owner)) {
|
||||
MOZ_ASSERT(!IsRelocated(),
|
||||
"Child is not relocated to editable combobox");
|
||||
rel.AppendTarget(owner);
|
||||
}
|
||||
}
|
||||
|
||||
return rel;
|
||||
}
|
||||
case RelationType::CONTROLLER_FOR: {
|
||||
Relation rel(new AssociatedElementsIterator(mDoc, mContent,
|
||||
nsGkAtoms::aria_controls));
|
||||
rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
|
||||
if (nsAccUtils::IsEditableARIACombobox(this)) {
|
||||
AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::aria_owns);
|
||||
while (Accessible* owned_child = iter.Next()) {
|
||||
MOZ_ASSERT(!owned_child->AsLocal()->IsRelocated());
|
||||
rel.AppendTarget(owned_child->AsLocal());
|
||||
}
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
|
||||
@@ -4080,11 +4098,17 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
|
||||
dom::HTMLLabelElement::FromNode(mContent)) {
|
||||
rel.AppendTarget(mDoc, labelEl->GetControl());
|
||||
}
|
||||
} else if (data.mType == RelationType::DETAILS) {
|
||||
} else if (data.mType == RelationType::DETAILS ||
|
||||
data.mType == RelationType::CONTROLLER_FOR) {
|
||||
// We need to use RelationByType for details because it might include
|
||||
// popovertarget. Nothing exposes an implicit reverse details
|
||||
// relation, so using RelationByType here is fine.
|
||||
rel = RelationByType(RelationType::DETAILS);
|
||||
//
|
||||
// We need to use RelationByType for controls because it might include
|
||||
// failed aria-owned relocations or it may be an output element.
|
||||
// Nothing exposes an implicit reverse controls relation, so using
|
||||
// RelationByType here is fine.
|
||||
rel = RelationByType(data.mType);
|
||||
} else {
|
||||
// We use an AssociatedElementsIterator here instead of calling
|
||||
// RelationByType directly because we only want to cache explicit
|
||||
|
||||
@@ -622,8 +622,8 @@ addAccessibleTask(
|
||||
async function (browser, accDoc) {
|
||||
const p = findAccessibleChildByID(accDoc, "p");
|
||||
const textbox = findAccessibleChildByID(accDoc, "textbox");
|
||||
testStates(textbox, 0, EXT_STATE_EDITABLE, 0, 0);
|
||||
|
||||
testStates(textbox, 0, EXT_STATE_EDITABLE, 0, 0);
|
||||
isnot(getAccessibleDOMNodeID(p.lastChild), "btn", "'p' owns relocated btn");
|
||||
is(textbox.value, "Hello");
|
||||
|
||||
@@ -700,3 +700,39 @@ addAccessibleTask(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
addAccessibleTask(
|
||||
`
|
||||
<div id="box" role="combobox"
|
||||
aria-owns="listbox"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-autocomplete="list"
|
||||
contenteditable="true"></div>
|
||||
<ul role="listbox" id="listbox">
|
||||
<li role="option">apple</li>
|
||||
<li role="option">peach</li>
|
||||
</ul>
|
||||
`,
|
||||
async (browser, accDoc) => {
|
||||
const combobox = findAccessibleChildByID(accDoc, "box");
|
||||
const listbox = findAccessibleChildByID(accDoc, "listbox");
|
||||
|
||||
testStates(combobox, 0, EXT_STATE_EDITABLE, 0, 0);
|
||||
is(combobox.childCount, 0, "combobox has no children");
|
||||
await testCachedRelation(combobox, RELATION_CONTROLLER_FOR, [listbox]);
|
||||
await testCachedRelation(listbox, RELATION_CONTROLLED_BY, [combobox]);
|
||||
|
||||
let expectedEvents = Promise.all([
|
||||
waitForStateChange(combobox, EXT_STATE_EDITABLE, false, true),
|
||||
waitForEvent(EVENT_REORDER, accDoc),
|
||||
]);
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.getElementById("box").contentEditable = false;
|
||||
});
|
||||
await expectedEvents;
|
||||
await testCachedRelation(combobox, RELATION_CONTROLLER_FOR, []);
|
||||
await testCachedRelation(listbox, RELATION_CONTROLLED_BY, []);
|
||||
is(combobox.childCount, 1, "combobox has listbox");
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user