зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1878366: Implement fallback to next valid ARIA role, r=Jamie
The following markup is a problem for Gecko: <nav role="region group">x</nav> The ARIA spec requires that "form" and "region" roles without accessible names be treated as if no role had been provided. It requires that user agents find a valid fallback role, or an implicit ARIA role if there's no fallback. Currently, Gecko would see "region" but no accessible name and fall back directly to the native role ("navigation"), skipping over the valid specified fallback of "group." This revision changes things. Now, if Gecko sees a region or form without an accessible name, we'll search through the role attribute string for the next valid (non-region, non-form) fallback role. If it doesn't find any, it will fall back to the element's native role. This revision also updates the expectations for a previously-failing web platform test. Differential Revision: https://phabricator.services.mozilla.com/D203338
This commit is contained in:
Родитель
7d9d21f181
Коммит
de5e31da37
|
@ -1480,7 +1480,8 @@ const nsRoleMapEntry* aria::GetRoleMap(dom::Element* aEl) {
|
||||||
return GetRoleMapFromIndex(GetRoleMapIndex(aEl));
|
return GetRoleMapFromIndex(GetRoleMapIndex(aEl));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
|
uint8_t aria::GetFirstValidRoleMapIndexExcluding(
|
||||||
|
dom::Element* aEl, std::initializer_list<nsStaticAtom*> aRolesToSkip) {
|
||||||
nsAutoString roles;
|
nsAutoString roles;
|
||||||
if (!aEl || !nsAccUtils::GetARIAAttr(aEl, nsGkAtoms::role, roles) ||
|
if (!aEl || !nsAccUtils::GetARIAAttr(aEl, nsGkAtoms::role, roles) ||
|
||||||
roles.IsEmpty()) {
|
roles.IsEmpty()) {
|
||||||
|
@ -1492,6 +1493,19 @@ uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
|
||||||
while (tokenizer.hasMoreTokens()) {
|
while (tokenizer.hasMoreTokens()) {
|
||||||
// Do a binary search through table for the next role in role list
|
// Do a binary search through table for the next role in role list
|
||||||
const nsDependentSubstring role = tokenizer.nextToken();
|
const nsDependentSubstring role = tokenizer.nextToken();
|
||||||
|
|
||||||
|
// Skip any roles that we aren't interested in.
|
||||||
|
bool shouldSkip = false;
|
||||||
|
for (nsStaticAtom* atomRole : aRolesToSkip) {
|
||||||
|
if (role.Equals(atomRole->GetUTF16String())) {
|
||||||
|
shouldSkip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSkip) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
size_t idx;
|
size_t idx;
|
||||||
auto comparator = [&role](const nsRoleMapEntry& aEntry) {
|
auto comparator = [&role](const nsRoleMapEntry& aEntry) {
|
||||||
return Compare(role, aEntry.ARIARoleString(),
|
return Compare(role, aEntry.ARIARoleString(),
|
||||||
|
@ -1508,6 +1522,11 @@ uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
|
||||||
return LANDMARK_ROLE_MAP_ENTRY_INDEX;
|
return LANDMARK_ROLE_MAP_ENTRY_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
|
||||||
|
// Get the rolemap index of the first valid role, excluding nothing.
|
||||||
|
return GetFirstValidRoleMapIndexExcluding(aEl, {});
|
||||||
|
}
|
||||||
|
|
||||||
const nsRoleMapEntry* aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex) {
|
const nsRoleMapEntry* aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex) {
|
||||||
switch (aRoleMapIndex) {
|
switch (aRoleMapIndex) {
|
||||||
case NO_ROLE_MAP_ENTRY_INDEX:
|
case NO_ROLE_MAP_ENTRY_INDEX:
|
||||||
|
|
|
@ -234,6 +234,19 @@ const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX;
|
||||||
*/
|
*/
|
||||||
const nsRoleMapEntry* GetRoleMap(dom::Element* aEl);
|
const nsRoleMapEntry* GetRoleMap(dom::Element* aEl);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the role map entry pointer's index for a given DOM node, skipping any
|
||||||
|
* given roles. This will use the first valid ARIA role if the role attribute
|
||||||
|
* provides a space delimited list of roles, excluding any given roles.
|
||||||
|
*
|
||||||
|
* @param aEl [in] the DOM node to get the role map entry for
|
||||||
|
* @param aRolesToSkip [in] the roles to skip when searching the role string
|
||||||
|
* @return the index of the pointer to the role map entry for the
|
||||||
|
* ARIA role, or NO_ROLE_MAP_ENTRY_INDEX if none
|
||||||
|
*/
|
||||||
|
uint8_t GetFirstValidRoleMapIndexExcluding(
|
||||||
|
dom::Element* aEl, std::initializer_list<nsStaticAtom*> aRolesToSkip);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the role map entry pointer's index for a given DOM node. This will use
|
* Get the role map entry pointer's index for a given DOM node. This will use
|
||||||
* the first ARIA role if the role attribute provides a space delimited list of
|
* the first ARIA role if the role attribute provides a space delimited list of
|
||||||
|
|
|
@ -1840,6 +1840,34 @@ bool LocalAccessible::SetCurValue(double aValue) {
|
||||||
kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
|
kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
role LocalAccessible::FindNextValidARIARole(
|
||||||
|
std::initializer_list<nsStaticAtom*> aRolesToSkip) const {
|
||||||
|
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
|
||||||
|
if (roleMapEntry && mContent && mContent->IsElement()) {
|
||||||
|
dom::Element* elem = mContent->AsElement();
|
||||||
|
if (!nsAccUtils::ARIAAttrValueIs(elem, nsGkAtoms::role,
|
||||||
|
roleMapEntry->roleAtom, eIgnoreCase)) {
|
||||||
|
// Get the next valid token that isn't in the list of roles to skip.
|
||||||
|
uint8_t roleMapIndex =
|
||||||
|
aria::GetFirstValidRoleMapIndexExcluding(elem, aRolesToSkip);
|
||||||
|
// If we don't find a valid token, fall back to the native role.
|
||||||
|
if (roleMapIndex == aria::NO_ROLE_MAP_ENTRY_INDEX ||
|
||||||
|
roleMapIndex == aria::LANDMARK_ROLE_MAP_ENTRY_INDEX) {
|
||||||
|
return NativeRole();
|
||||||
|
}
|
||||||
|
const nsRoleMapEntry* fallbackRoleMapEntry =
|
||||||
|
aria::GetRoleMapFromIndex(roleMapIndex);
|
||||||
|
if (!fallbackRoleMapEntry) {
|
||||||
|
return NativeRole();
|
||||||
|
}
|
||||||
|
// Return the next valid role, but validate that first, too.
|
||||||
|
return ARIATransformRole(fallbackRoleMapEntry->role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall back to the native role.
|
||||||
|
return NativeRole();
|
||||||
|
}
|
||||||
|
|
||||||
role LocalAccessible::ARIATransformRole(role aRole) const {
|
role LocalAccessible::ARIATransformRole(role aRole) const {
|
||||||
// Beginning with ARIA 1.1, user agents are expected to use the native host
|
// Beginning with ARIA 1.1, user agents are expected to use the native host
|
||||||
// language role of the element when the form or region roles are used without
|
// language role of the element when the form or region roles are used without
|
||||||
|
@ -1854,7 +1882,17 @@ role LocalAccessible::ARIATransformRole(role aRole) const {
|
||||||
// another example of why we should consider caching the accessible name. See:
|
// another example of why we should consider caching the accessible name. See:
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
|
||||||
if (aRole == roles::REGION || aRole == roles::FORM) {
|
if (aRole == roles::REGION || aRole == roles::FORM) {
|
||||||
return NameIsEmpty() ? NativeRole() : aRole;
|
if (NameIsEmpty()) {
|
||||||
|
// If we have a "form" or "region" role, but no accessible name, we need
|
||||||
|
// to search for the next valid role. First, we search through the role
|
||||||
|
// attribute value string - there might be a valid fallback there. Skip
|
||||||
|
// all "form" or "region" attributes; we know they're not valid since
|
||||||
|
// there's no accessible name. If we find a valid role that's not "form"
|
||||||
|
// or "region", fall back to it (but run it through ARIATransformRole
|
||||||
|
// first). Otherwise, fall back to the element's native role.
|
||||||
|
return FindNextValidARIARole({nsGkAtoms::region, nsGkAtoms::form});
|
||||||
|
}
|
||||||
|
return aRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: these unfortunate exceptions don't fit into the ARIA table. This is
|
// XXX: these unfortunate exceptions don't fit into the ARIA table. This is
|
||||||
|
|
|
@ -1010,6 +1010,17 @@ class LocalAccessible : public nsISupports, public Accessible {
|
||||||
*/
|
*/
|
||||||
nsIFrame* FindNearestAccessibleAncestorFrame();
|
nsIFrame* FindNearestAccessibleAncestorFrame();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function assumes that the current role is not valid. It searches for a
|
||||||
|
* fallback role in the role attribute string, and returns it. If there is no
|
||||||
|
* valid fallback role in the role attribute string, the function returns the
|
||||||
|
* native role. The aRolesToSkip parameter will cause the function to skip any
|
||||||
|
* roles found in the role attribute string when searching for the next valid
|
||||||
|
* role.
|
||||||
|
*/
|
||||||
|
role FindNextValidARIARole(
|
||||||
|
std::initializer_list<nsStaticAtom*> aRolesToSkip) const;
|
||||||
|
|
||||||
LocalAccessible* GetPopoverTargetDetailsRelation() const;
|
LocalAccessible* GetPopoverTargetDetailsRelation() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[fallback-roles.html]
|
|
||||||
[fallback role w/ region with no label]
|
|
||||||
expected: FAIL
|
|
Загрузка…
Ссылка в новой задаче