Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update implicit role calculations in ARIAMapper #574

Merged
merged 6 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion accessibility-checker-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ The accessibility report is in JSON format, and contains information about the i
// xpath
"dom": "/html[1]",
// path of ARIA roles
"aria": ""
"aria": "/document[1]"
},
"ruleTime": 0,
// Generated message
Expand Down
106 changes: 55 additions & 51 deletions accessibility-checker-engine/src/v2/aria/ARIAMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,21 @@ export class ARIAMapper extends CommonMapper {
return accumulated.trim();
}
}

// Since nodeToRole calls back here for form and section, we need special casing here to handle those two cases
if (["section", "form"].includes(cur.nodeName.toLowerCase())) {
if (elem.hasAttribute("aria-label") && elem.getAttribute("aria-label").trim().length > 0) {
// If I'm not an embedded control or I'm not recursing, return the aria-label
if (!labelledbyTraverse && !walkTraverse) {
return elem.getAttribute("aria-label").trim();
}
}
if (elem.hasAttribute("title")) {
return elem.getAttribute("title");
}
return "";
}

// 2c. If label or walk, and this is a control, skip to the value, otherwise provide the label
const role = ARIAMapper.nodeToRole(cur);
let isEmbeddedControl = [
Expand Down Expand Up @@ -579,36 +594,33 @@ export class ARIAMapper extends CommonMapper {
}
return false;
}

private static inputToRoleMap = (function() {
let menuButtonCheck = function(element) {
return ARIAMapper.hasParentRole(element, "menu") ? "menuitem" : "button";
};
let textSuggestions = function(element) {
let hasList = function(element) {
if (element.hasAttribute("list")) {
let id = element.getAttribute("list");
let idRef = FragmentUtil.getById(element, id);
if (idRef && idRef.nodeName.toLowerCase() === "datalist") {
return "combobox";
return true;
}
}
return "textbox";
return false;
};
let textSuggestions = function(element) {
return hasList(element) ? "combobox" : "textbox";
}
return {
"button": menuButtonCheck,
"image": menuButtonCheck,
"checkbox": function(element) {
return ARIAMapper.hasParentRole(element, "menu") ? "menuitemcheckbox" : "checkbox";
},
"radio": function(element) {
return ARIAMapper.hasParentRole(element, "menu") ? "menuitemradio" : "radio";
},
"button": "button",
"image": "button",
"checkbox": "checkbox",
"radio": "radio",
"email": textSuggestions,
"search": textSuggestions,
"search": function(element) {
return hasList(element) ? "combobox" : "searchbox";
},
"tel": textSuggestions,
"text": textSuggestions,
"url": textSuggestions,
"password": "textbox",
"number": "spinbutton",
"range": "slider",
"reset": "button",
Expand Down Expand Up @@ -662,8 +674,7 @@ export class ARIAMapper extends CommonMapper {
"a": function(element) {
// If it doesn't represent a hyperlink, no corresponding role
if (!element.hasAttribute("href")) return null;
// If link is in a menu, it's a menuitem, otherwise it's a link
return ARIAMapper.hasParentRole(element, "menu") ? "menuitem" : "link";
return "link";
},
"area": function(element) {
// If it doesn't represent a hyperlink, no corresponding role
Expand All @@ -672,26 +683,32 @@ export class ARIAMapper extends CommonMapper {
},
"article": "article",
"aside": "complementary",
"body": "document",
"button": "button",
"datalist": "listbox",
"dd": "definition",
"details": "group",
"dfn": "term",
"dialog": "dialog",
"dt": "term",
"fieldset": "group",
"figure": "figure",
"footer": function(element) {
let parent = DOMUtil.parentNode(element);
let nodeName = parent.nodeName.toLowerCase();
// If nearest sectioningRoot or sectioningContent is body
while (parent) {
while (parent && parent.nodeType === 1) {
let nodeName = parent.nodeName.toLowerCase();
if (sectioningRoots[nodeName] || sectioningContent[nodeName]) {
return (nodeName === "body") ? "contentinfo" : null;
}
parent = DOMUtil.parentNode(parent);
nodeName = parent.nodeName.toLowerCase();
}
return null;
},
"form": "form",
"form": function(element) {
let name = ARIAMapper.computeName(element);
return (name && name.trim().length > 0) ? "form" : null;
},
// TODO "form-associated custom element"
"h1": "heading",
"h2": "heading",
"h3": "heading",
Expand All @@ -711,6 +728,7 @@ export class ARIAMapper extends CommonMapper {
return null;
},
"hr": "separator",
"html": "document",
"img": function(element) {
if (element.hasAttribute("alt") && element.getAttribute("alt").length === 0) {
return "presentation";
Expand All @@ -719,53 +737,39 @@ export class ARIAMapper extends CommonMapper {
}
},
"input": inputToRole,
"keygen": "listbox",
"keygen": "listbox", // deprecated, but keep for backward compat
"li": "listitem",
"main": "main",
"math": "math",
"menu": function(element) {
if (!element.hasAttribute("type")) return null;
let eType = element.getAttribute("type").toLowerCase();
if (eType === "context") return "menu";
if (eType === "toolbar") return "toolbar";
return null;
},
"menuitem": function(element) {
// Default type is command
if (!element.hasAttribute("type")) return "menuitem";
let eType = element.getAttribute("type").toLowerCase();
if (eType.trim().length === 0) return "menuitem";

if (eType === "command") return "menuitem";
if (eType === "checkbox") return "menuitemcheckbox";
if (eType === "radio") return "menuitemradio";
return null;
},
"meter": "progressbar",
"menu": "list",
"nav": "navigation",
"ol": "list",
"optgroup": "group",
"option": "option",
"output": "status",
"progress": "progressbar",
"section": "region",
"section": function(element) {
let name = ARIAMapper.computeName(element);
return (name && name.trim().length > 0) ? "region" : null;
},
"select": function(element) {
if (element.hasAttribute("multiple") || (element.hasAttribute("size") && parseInt(element.getAttribute("size")) > 1)) {
if (element.hasAttribute("multiple") || (RPTUtil.attributeNonEmpty(element, "size") && parseInt(element.getAttribute("size")) > 1)) {
return "listbox";
} else {
return "combobox";
}
},
"summary": "button",
"svg": "graphics-document",
"table": "table",
"textarea": "textbox",
"tbody": "rowgroup",
"textarea": "textbox",
"td": function(element) {
let parent = DOMUtil.parentNode(element);
while (parent) {
let role = ARIAMapper.nodeToRole(parent);
if (role === "table") return "cell";
if (role === "grid") return "gridcell";

if (role === "grid" || role === "treegrid") return "gridcell";
parent = DOMUtil.parentNode(parent);
}
return null;
Expand Down Expand Up @@ -802,8 +806,8 @@ export class ARIAMapper extends CommonMapper {

// scope is auto, default (without a scope) or invalid value.
// if all the sibling elements are th, then return "columnheader"
var siblings = element => [...element.parentElement.children].filter(node=>node.nodeType == 1 && node.tagName != "TH");
if (siblings == null || siblings.length == 0)
var siblings = element => [...element.parentElement.children].filter(node=>node.nodeType === 1 && node.tagName != "TH");
if (siblings === null || siblings.length === 0)
return "columnheader";
else return "rowheader";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ let a11yRulesImg: Rule[] = [
context: "aria:graphics-document,aria:graphics-symbol",
run: (context: RuleContext, options?: {}): RuleResult | RuleResult[] => {
const ruleContext = context["dom"].node as Element;
if (!ruleContext.hasAttribute("role") || !ruleContext.getAttribute("role").includes("graphics-")) return null;
/* removed the role check role= presentation since if an element has role=img, then there needs to be a check for alt attribute regardless of the presecne of role=presentation
if (RPTUtil.hasRole(ruleContext, "presentation") || RPTUtil.hasRole(ruleContext, "none")){
return RulePass(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let a11yRulesInput: Rule[] = [
id: "WCAG20_Input_ExplicitLabel",
context: "aria:button,aria:checkbox,aria:combobox,aria:listbox,aria:menuitemcheckbox"
+",aria:menuitemradio,aria:radio,aria:searchbox,aria:slider,aria:spinbutton"
+",aria:switch,aria:textbox,aria:progressbar,dom:input[type=file],dom:output",
+",aria:switch,aria:textbox,aria:progressbar,dom:input[type=file],dom:output,dom:meter,dom:input[type=password]",
// the datalist element do not require any explicit or implicit label, might need to exclude it from the scope of the rules
run: (context: RuleContext, options?: {}): RuleResult | RuleResult[] => {
const ruleContext = context["dom"].node as Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,32 @@
</head>

<body id="id-9" aria-atomic="true">
<p>How many circles are there?</p>
<svg xmlns="https://www.w3.org/2000/svg">
<circle
role="graphics-symbol"
cx="50"
cy="50"
r="40"
stroke="green"
stroke-width="4"
fill="yellow"
aria-label="1 circle"
></circle>
</svg>
<p>How many circles are there?</p>
<svg xmlns="https://www.w3.org/2000/svg">
<circle role="graphics-symbol" cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow"
aria-label="1 circle"></circle>
</svg>

<script type="text/javascript">
UnitTest = {
ruleIds: ["HAAC_Aria_SvgAlt"],
results: [
{
"ruleId": "HAAC_Aria_SvgAlt",
"value": [
"INFORMATION",
"PASS"
],
"path": {
"dom": "/html[1]/body[1]/svg[1]/circle[1]",
"aria": "/document[1]/graphics-symbol[1]"
},
"reasonId": "Pass_0",
"message": "Rule Passed",
"messageArgs": [],
"apiArgs": [],
"category": "Accessibility"
}
{
"ruleId": "HAAC_Aria_SvgAlt",
"value": [
"INFORMATION",
"PASS"
],
"path": {
"dom": "/html[1]/body[1]/svg[1]/circle[1]",
"aria": "/document[1]/graphics-document[1]/graphics-symbol[1]"
},
"reasonId": "Pass_0",
"message": "Rule Passed",
"messageArgs": [],
"apiArgs": [],
"category": "Accessibility"
}
]
}
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
</head>

<body id="id-9" aria-atomic="true">
<svg id="target"><title><g>Time II: Party</g></title></svg>
<svg id="target">
<title>
<g>Time II: Party</g>
</title>
</svg>
<script type="text/javascript">
UnitTest = {
ruleIds: ["HAAC_Aria_SvgAlt"],
results: [

]
results: []
}
</script>
</body>
Expand Down
Loading