Skip to content

Commit

Permalink
Merge pull request #1029 from IBMa/dev-600
Browse files Browse the repository at this point in the history
Fix the issue related to the required context role
  • Loading branch information
ErickRenteria authored Aug 11, 2022
2 parents 0d6669b + f8c4e0a commit 910c787
Show file tree
Hide file tree
Showing 61 changed files with 3,847 additions and 1,583 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,20 @@ <h3 id="ruleMessage"></h3>

### Why is this important?

If a ARIA `role` attribute is specified for an element, any ARIA child elements required for that role must also be present. Child elements' native ARIA roles satisfy the requirement - the role does not have to be explicitly set. Without the required child elements, assistive technologies may not be able to accurately represent or interact with the element.
If an ARIA `role` attribute is specified for an element, the child elements required for that role must also be present.
Child elements' implied (native HTML) roles may satisfy the requirement - the role does not have to be explicitly set by ARIA.
Any incorrect child element roles also need to be removed. For example, `role="group"` is not a valid child of `list`.
Without the correct required child element roles, assistive technologies may not be able to accurately represent the context or be able to interact with the element.

<!-- This is where the code snippet is injected -->
<div id="locSnippet"></div>

### What to do

* Add the appropriate required child element(s) to this element.
* Add the appropriate required child element(s) to this element
* *Or*, remove any incorrect child element(s)

For example, the following element uses the `"radiogroup"` role. The `"radiogroup"` role requires child elements with `role="radio"` set in a manner that represents the checked or unchecked state of the radio button.
Example with a `"radiogroup"` that requires child elements with `role="radio"` set in a manner that represents the checked or unchecked state of the radio button:

```
<div role="radiogroup">
Expand All @@ -68,8 +72,8 @@ <h3 id="ruleMessage"></h3>
<table role= "treegrid">
<tr>
<td>
element with role= "treegrid" requires child element with role="row".
TR has implicit ARIA role="row", so the condition is met
element with role="treegrid" requires child element with role="row".
TR has implicit ARIA role="row", so the condition is met.
<td>
<tr>
</table>
Expand All @@ -87,7 +91,8 @@ <h3 id="ruleMessage"></h3>
### About this requirement

* [IBM 4.1.2 Name, Role, Value](https://www.ibm.com/able/requirements/requirements/#4_1_2)
* [ARIA practices - Required Owned Elements](https://w3c.github.io/aria-practices/#mustContain)
* [ARIA authoring practices guide](https://www.w3.org/WAI/ARIA/apg/)
* [ARIA specification - Required Owned Elements](https://w3c.github.io/aria/#mustContain)

### Who does this affect?

Expand Down
2 changes: 1 addition & 1 deletion accessibility-checker-engine/legacy/wrapper-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ var mapRuleToG = IBMa.mapRuleToG = {
"IBMA_Color_Contrast_WCAG2AA_PV": "1149",
"WCAG20_Body_FirstASkips_Native_Host_Sematics": "1150",
"WCAG20_Body_FirstAContainsSkipText_Native_Host_Sematics": "1151",
"Rpt_Aria_RequiredChildren_Native_Host_Sematics": "1152",
"aria_child_valid": "1152",
"Rpt_Aria_RequiredParent_Native_Host_Sematics": "1153",
"Rpt_Aria_EventHandlerMissingRole_Native_Host_Sematics": "1154",
"Rpt_Aria_WidgetLabels_Implicit": "1156",
Expand Down
2 changes: 1 addition & 1 deletion accessibility-checker-engine/legacy/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ var mapRuleToG = IBMa.mapRuleToG = {
"IBMA_Color_Contrast_WCAG2AA_PV": "1149",
"WCAG20_Body_FirstASkips_Native_Host_Sematics": "1150",
"WCAG20_Body_FirstAContainsSkipText_Native_Host_Sematics": "1151",
"Rpt_Aria_RequiredChildren_Native_Host_Sematics": "1152",
"aria_child_valid": "1152",
"Rpt_Aria_RequiredParent_Native_Host_Sematics": "1153",
"Rpt_Aria_EventHandlerMissingRole_Native_Host_Sematics": "1154",
"Rpt_Aria_WidgetLabels_Implicit": "1156",
Expand Down
29 changes: 23 additions & 6 deletions accessibility-checker-engine/src/v2/aria/ARIADefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2098,11 +2098,6 @@ export class ARIADefinitions {
validRoles: ["any"],
globalAriaAttributesValid: true
},
"tbody": {
implicitRole: ["rowgroup"],
validRoles: ["any"],
globalAriaAttributesValid: true
},
"template": {
implicitRole: null,
validRoles: null,
Expand Down Expand Up @@ -2524,6 +2519,28 @@ export class ARIADefinitions {
otherDisallowedAriaAttributes: ["aria-multiselectable"]
}
},
"tbody": {
"des-table": {
implicitRole: ["rowgroup"],
validRoles: ["any"],
globalAriaAttributesValid: true
},
"des-grid": {
implicitRole: ["rowgroup"],
validRoles: ["any"],
globalAriaAttributesValid: true
},
"des-treegrid": {
implicitRole: ["rowgroup"],
validRoles: ["any"],
globalAriaAttributesValid: true
},
"des-other": {
implicitRole: null,
validRoles: ["any"],
globalAriaAttributesValid: true
}
},
"td": {
"des-table": {
implicitRole: ["cell"],
Expand Down Expand Up @@ -2585,7 +2602,7 @@ export class ARIADefinitions {
globalAriaAttributesValid: true
},
"des-other": {
implicitRole: ["row"],
implicitRole: null,
validRoles: ["any"],
globalAriaAttributesValid: true
}
Expand Down
20 changes: 18 additions & 2 deletions accessibility-checker-engine/src/v2/aria/ARIAMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,15 @@ export class ARIAMapper extends CommonMapper {
"summary": "button",
"svg": "graphics-document",
"table": "table",
"tbody": "rowgroup",
"tbody": function(element) {
let parent = DOMUtil.parentNode(element);
while (parent) {
let role = ARIAMapper.nodeToRole(parent);
if (role === "table" || role === "grid" || role === "treegrid") return "rowgroup";
parent = DOMUtil.parentNode(parent);
}
return null;
},
"textarea": "textbox",
"td": function(element) {
let parent = DOMUtil.parentNode(element);
Expand Down Expand Up @@ -918,7 +926,15 @@ export class ARIAMapper extends CommonMapper {
},
"tfoot": "rowgroup",
"thead": "rowgroup",
"tr": "row",
"tr": function(element) {
let parent = DOMUtil.parentNode(element);
while (parent) {
let role = ARIAMapper.nodeToRole(parent);
if (role === "table" || role === "grid" || role === "treegrid") return "row";
parent = DOMUtil.parentNode(parent);
}
return null;
},
"ul": "list"
}
})()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ export class RPTUtil {
*
* @memberOf RPTUtil
*/
public static getImplicitRole(ele) {
public static getImplicitRole(ele) : string[] {
let tagProperty = RPTUtil.getElementAriaProperty(ele);
//check if there are any implicit roles for this element.
if (tagProperty) {
Expand Down Expand Up @@ -1191,7 +1191,6 @@ export class RPTUtil {
}
} else if (tagNames.length) {
for (let idx in tagNames) {
// Packages.java.lang.System.err.println(thisTag + ":" + tagNames[idx] + ":" + (tagNames[idx] === thisTag));
if (tagNames[idx] === thisTag)
return walkNode;
}
Expand Down Expand Up @@ -1536,6 +1535,121 @@ export class RPTUtil {
return descendants;
}

/**
* This function is responsible for getting All direct children in AT tree with a role (exclude none and presentation)
*
* @parm {element} element - parent element for which we will be checking children for
* @return {node} - The direct child elements in AT tree that has a role
*
* @memberOf RPTUtil
*/
public static getDirectATChildren(element) {
let requiredChildRoles = RPTUtil.getRequiredChildRoles(element, true);
let direct: Array<HTMLElement> = [];
RPTUtil.retrieveDirectATChildren(element, requiredChildRoles, direct);
return direct;
}

/**
* This function is responsible for recursively any child path till either no child or a child with a role is found (exclude none and presentation)
*
* @parm {element} element - parent element for which we will be checking children for
* @return {node} - The direct child elements in AT tree
*
* @memberOf RPTUtil
*/
public static retrieveDirectATChildren(element, requiredChildRoles, direct: Array<HTMLElement>) {
let children : HTMLElement[] = [];
if (element.children !== null && element.children.length > 0) {
for (let i=0; i < element.children.length; i++) {
children.push(element.children[i]);
}
}
// if the element contains "aria-own" attribute, then the aria-owned children need to be included too
let owned = element.getAttribute("aria-owns");
if (owned) {
let doc = element.ownerDocument;
if (doc) {
let ownedIds = owned.split(" ");
for (let i=0; i < ownedIds.length; i++) {
let ownedElem = doc.getElementById(ownedIds[i]);
if (ownedElem) {
children.push(ownedElem);
}
}
}
}
if (children.length > 0) {
for (let i=0; i < children.length; i++) {
//ignore hidden and invisible child
if (RPTUtil.isNodeHiddenFromAT(children[i]) || !RPTUtil.isNodeVisible(children[i])) continue;
let roles = RPTUtil.getRoles(children[i], false);
if (roles === null || roles.length === 0) {
roles = RPTUtil.getImplicitRole(children[i]);
}

if (roles !== null && roles.length > 0) {
//remove 'none' and 'presentation'
roles = roles.filter(function(role) {
return role !== "none" && role !== "presentation";
})

// a 'group' role is allowed but not required for some elements so remove it if exists
if (roles.includes("group") && requiredChildRoles && requiredChildRoles.includes('group')) {
roles = roles.filter(function(role) {
return role !== 'group';
})
}
}
if (roles !== null && roles.length > 0) {
direct.push(children[i]);
} else {
// recursive until get a return value,
RPTUtil.retrieveDirectATChildren(children[i], requiredChildRoles, direct);
}
}
}
return null;
}

/**
* this function returns null or required child roles for a given element with one more roles,
* return null if the role is 'none' or 'presentation'
* @param element
* @param includeImplicit include implicit roles if no role is explicitly provided
* @returns
*/
public static getRequiredChildRoles(element, includeImplicit: boolean) : string[] {
let roles = RPTUtil.getRoles(element, false);
// if explicit role doesn't exist, get the implicit one
if ((!roles || roles.length === 0) && includeImplicit) {
roles = RPTUtil.getImplicitRole(element);
}

/**
* ignore if the element doesn't have any explicit or implicit role
*/
if (!roles || roles.length == 0) {
return null;
}

/**
* ignore if the element contains none or presentation role
*/
let presentationRoles = ["none", "presentation"];
const found = roles.some(r => presentationRoles.includes(r));
if (found) return null;

let designPatterns = ARIADefinitions.designPatterns;
let requiredChildRoles: string[] = new Array();
for (let j = 0; j < roles.length; ++j) {
if (designPatterns[roles[j]] && designPatterns[roles[j]].reqChildren !== null) {
requiredChildRoles = RPTUtil.concatUniqueArrayItemList(designPatterns[roles[j]].reqChildren, requiredChildRoles);
}
}
return requiredChildRoles;
}

/**
* This function is responsible for getting an element referenced by aria-owns and has the
* role that was specified.
Expand Down Expand Up @@ -2337,6 +2451,7 @@ export class RPTUtil {
else
tagProperty = specialTagProperties["no-multiple-attr-size-gt1"];
break;
case "tbody":
case "td":
case "th":
case "tr":
Expand Down
Loading

0 comments on commit 910c787

Please sign in to comment.