-
Notifications
You must be signed in to change notification settings - Fork 791
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
feat(new rule): aria-allowed-role #623
Conversation
A couple of remarks: No support yet for SVG elements. It currently includes DPUB roles. It validates against the HTML-ARIA spec, but not against the HTML spec. E.g. It currently includes a specific check for The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few comments while you're working on this.
lib/rules/aria-allowed-role.json
Outdated
@@ -0,0 +1,18 @@ | |||
{ | |||
"id": "allowed-role", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure the ID is consistent with the file name. I think aria-role-allowed
might be a tiny bit better as an ID?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -0,0 +1,207 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a lot of the functions and ARIA data should be made into a utility function. Probably on axe.commons.aria
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
lib/checks/aria/allowed-role.json
Outdated
"id": "aria-allowed-role", | ||
"evaluate": "allowed-role.js", | ||
"metadata": { | ||
"impact": "critical", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be minor
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
lib/rules/aria-allowed-role.json
Outdated
{ | ||
"id": "aria-allowed-role", | ||
"selector": "[role]", | ||
"tags": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should only contain cat.aria
and best-practice
. Rules are either a best practice or a WCAG violation. It's never both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -0,0 +1,241 @@ | |||
describe('aria-allowed-role', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this many checks here. The goal of these integration checks is to make sure the logic works. We're less concerned here with exactly which element does what. Those kinds of tests are better done as integration tests. Have a look at the following PR for an example:
https://github.com/dequelabs/axe-core/pull/629/files
lib/checks/aria/allowed-role.js
Outdated
var tagName = node.tagName.toLowerCase(); | ||
|
||
// check if tag is allowed to have a role | ||
if(elementsHasNoAllowedRoles.indexOf(tagName) > -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider allowing users to whitelist certain elements through options, like we did with some of the other aria checks. Have a look here how this works:
https://github.com/dequelabs/axe-core/blob/develop/lib/checks/aria/valid-attr.js#L11
lib/checks/aria/allowed-role.js
Outdated
* @return {HTMLElement} Parent that matches one of the tagnames | ||
*/ | ||
function getParentWithTagName(el, tagName) { | ||
while (el.parentNode){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some inconsistent spacing around the while and if declarations here, as well as the comment blocks. Maybe we should add an automated tool to clean that stuff up. WDYT about adding Prettier in a separate PR, Wilco?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm open to experimenting with Prettier. It seemed pretty cool. I don't think we should start with axe-core for that. Maybe a less impactful project to try it out on and adopt it into others if we like it.
Thanks so much for working on this PR! Really cool to see it coming together. |
I'm writing some issues down that I have while implementing this PR, it's mostly my backlog of things that may need a fix in the future or I'm uncertain it's the correct conclusion and needs some further reading.
|
@@ -376,8 +376,7 @@ lookupTables.role = { | |||
one: ['rowgroup', 'row'] | |||
}, | |||
nameFrom: ['author'], | |||
context: null, | |||
implicit: ['table'] | |||
context: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated only the implicit roles who needed a update for this new rule to validate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the reason for removing the implicit table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://www.w3.org/TR/html-aria/#table - An table element only has an implicit role of table following the current spec.
lib/checks/aria/allowed-role.js
Outdated
// any implicit role is disallowed | ||
if (!options.allowImplicit && role === implicitRole) { | ||
// edge case: setting implicit role row on tr element is allowed when child of table[role='grid'] | ||
if (!(role === 'row' && tagName === 'tr' && node.matches('table[role="grid"] :scope'))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An edge case where setting a implicit role is allowed, only applicable in the current spec when using <table role='grid'><td role='cell'></td></table>
. I don't like the current location of the check...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node.matches()
doesn't work in phantomJS. You can use the polyfill in axe.commons.utils.matchesSelector
instead though.
I added a option that disables the check on implicit roles, e.g. currently What is opinion about the current setup within the Have you any preference how to setup beneath mention cases: |
@lemnis Thank you so very much for the contribution. This is quite a big one, so I'm going to have to set some time aside to properly review it. That may take a little while. The first and most obvious thing is that your tests are failing. Have a look at CircleCI to see what's going wrong. You can run your tests locally by calling |
* @param {Element} node element | ||
* @return {String} normalized tagName | ||
*/ | ||
axe.utils.getTagName = function(node) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the value of this is. So far we've solved this by always doing tagName.toUpperCase()
for everything. I think we should stick with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is / was mostly specific to svg, rereading some documents my case isn't entirely true anymore.
For an a
element in different context it would return:
<a href="#">Hello</a>
, tagName will return A
and the function will return a
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="#" target="_top">World</a></svg>
, the tagName and function will return a
Colors
For svg gradients you will get:
<svg><linearGradient></linearGradient></svg>
tagName and the function will return linearGradient
What I previously thought (svg2 spec):
<svg><solidcolor></solidcolor></svg>
tagName and the function will return solidcolor
But it changed to mimic the name conventions in previously used, so when it will be implemented it will be parsed as:
<svg><solidColor></solidColor></svg>
tagName and the function willreturn solidColor
I prefer to use the exact name that is shown in the DOM, so an lowercase name for html elements and camelcased names for svg elements.
Also svg2 supports html elements inside an svg, I didn't bother to add the logic as at this moment in time there is no browser support, e.g.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
<html:video src="http://example.org/dummyvideo" controls="controls">
<html:p>The video format is not supported by this browser.</html:p>
</html:video>
</svg>
So (currently) the P element returns an tagName of html:p
, but the function should return p
.
Conclusion: The function could be removed now, as (I believe) this rule is the only rule that supports svg elements. An type of this function will be needed in the future. A better solution would probably be to check always the tagname with its namespaceURI together. So a
elements within svg elements can never mess up the end results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I think you're right about the tagName + namespace approach. We shouldn't try to solve that here though. One problem at a time please :). I think we stick to the existing approach for this rule (e.g. .tagName.toUpperCase()
) and create a separate issue / PR for this. Can you put together a new issue outlining what the problems are with the current way we're handling tag names?
lib/commons/aria/roles.js
Outdated
if(node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { | ||
var rawEpubRoles = node.getAttributeNS('http://www.idpf.org/2007/ops', 'type'); | ||
var epubRoles = rawEpubRoles.split(' '); | ||
roles = roles.concat(epubRoles.forEach((e, i, a) => a[i] = 'doc-' + e)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use epubRoles.map(role => 'doc-' + role)
here to simplify this a bit.
lib/commons/aria/roles.js
Outdated
aria.getRoles = function(node) { | ||
var roles = []; | ||
if(node.hasAttribute('role')){ | ||
roles = roles.concat(node.getAttribute('role').split(' ')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be more robust. Probably split on any whitespace character, and filter out any empty values that might occur with spaces at the start or end of the string.
lib/checks/aria/allowed-role.js
Outdated
return true; | ||
} | ||
|
||
roles.forEach(function(role) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use roles.reduce()
here to create the disallowedRoles, it's much clearer what this loop is supposed to do. You don't have to do the allowed
boolean either if you do that. You simply do return disallowed.concat(role);
and go on to the next role.
lib/checks/aria/allowed-role.js
Outdated
// any implicit role is disallowed | ||
if (!options.allowImplicit && role === implicitRole) { | ||
// edge case: setting implicit role row on tr element is allowed when child of table[role='grid'] | ||
if (!(role === 'row' && tagName === 'tr' && node.matches('table[role="grid"] :scope'))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node.matches()
doesn't work in phantomJS. You can use the polyfill in axe.commons.utils.matchesSelector
instead though.
lib/checks/aria/allowed-role.js
Outdated
var disallowedRoles = []; | ||
|
||
// check if check on the element is disabled through the options | ||
if (options.disable && options.disable.indexOf(tagName) > -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be called options.ignoredTags
, so its purpose is clearer.
}); | ||
|
||
if (disallowedRoles.length) { | ||
// this.data(disallowedRoles); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to uncomment this. You may want to write some tests to make sure this is working as well.
@@ -376,8 +376,7 @@ lookupTables.role = { | |||
one: ['rowgroup', 'row'] | |||
}, | |||
nameFrom: ['author'], | |||
context: null, | |||
implicit: ['table'] | |||
context: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the reason for removing the implicit table?
@@ -388,7 +387,7 @@ lookupTables.role = { | |||
owned: null, | |||
nameFrom: ['author', 'contents'], | |||
context: ['row'], | |||
implicit: ['td', 'th'] | |||
implicit: ['th'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for removing td
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://www.w3.org/TR/html-aria/#TD - An td never has an implicit role of gridcell following the current spec.
|
||
// check within if the element has an allowed subset of roles | ||
if (allowed) { | ||
allowed = axe.commons.aria.isAllowedRole(tagName, role); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think isAllowedRole
should include the elementCheck[tagName](node, role);
call you make. It seems to me the current isAllowedRole
doesn't tell the full story, and wouldn't ever be called separate from the element checks. I suggest putting them into their own file, commons/aria/has-allowed-role.js
. You get better encapsulation that way.
|
As mentioned at #97 (comment).
It's still work in progress, but I want to get some early feedback. My current questions are:
Things yet to do: