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

feat(new rule): aria-allowed-role #623

Closed
wants to merge 10 commits into from
Closed

Conversation

lemnis
Copy link

@lemnis lemnis commented Nov 24, 2017

As mentioned at #97 (comment).

It's still work in progress, but I want to get some early feedback. My current questions are:

  • Should the arrays stored somewhere else?
  • Are the values inside the json set correcty?
  • Which tags should be used?, I'm rather new to the whole wcag spec.

Things yet to do:

  • add tests
  • remove the arrow functions
  • add more comments to the code

@lemnis lemnis changed the title WIP, new rule: allowed-role WIP, new rule: aria-allowed-role Nov 27, 2017
@lemnis
Copy link
Author

lemnis commented Nov 29, 2017

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. <button role='option'> is allowed in HTML-ARIA, but not in the current HTML rec.

It currently includes a specific check for <button type='menu'>. This button type was proposed in HTML 5.1 spec but dropped in version 5.2. It never had any browser support. As result when getting the type property, it will return the default value of submit. I could get original value with getAttribute() or removing this specific check. I am leaning to the later.

The menu element has one defined and default value for the type attribute in the HTML 5.1 spec, currently, the element is still mentioned in the living standard but without the type attribute. And the whole element is removed from HTML 5.2. I haven't decided yet what to do with this specific element.

Copy link
Contributor

@WilcoFiers WilcoFiers left a 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.

@@ -0,0 +1,18 @@
{
"id": "allowed-role",
Copy link
Contributor

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?

Copy link
Contributor

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 @@
/**
Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"id": "aria-allowed-role",
"evaluate": "allowed-role.js",
"metadata": {
"impact": "critical",
Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
"id": "aria-allowed-role",
"selector": "[role]",
"tags": [
Copy link
Contributor

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.

Copy link
Contributor

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 () {
Copy link
Contributor

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

var tagName = node.tagName.toLowerCase();

// check if tag is allowed to have a role
if(elementsHasNoAllowedRoles.indexOf(tagName) > -1) {
Copy link
Contributor

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

* @return {HTMLElement} Parent that matches one of the tagnames
*/
function getParentWithTagName(el, tagName) {
while (el.parentNode){
Copy link
Contributor

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?

Copy link
Contributor

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.

@marcysutton
Copy link
Contributor

Thanks so much for working on this PR! Really cool to see it coming together.

@lemnis
Copy link
Author

lemnis commented Dec 8, 2017

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.
They could already have been tackled.

  • general SVG support, e.g., having the implicit graphics-symbol role on circle elements
  • having the same result while validating attributes who are in a different namespace but who should have a similar logic as its HTML sibling, e.g.
    • <section epub:type="dedication"> == <section role="doc-dedication">
    • <svg><a xlink:href="#"></a></svg> == <svg><a href="#"></a></svg>
      (both attributes bind its value to the href property)
  • validating elements against the correct namespace, e.g.
    • <svg xmlns:html="http://www.w3.org/1999/xhtml"><html:video></html:video></svg> == <video></video>
    • <svg><svg id="foo"></svg></svg> != <html><svg id="foo"></svg></html>
  • outdated implicit selectors within the lookup tables, e.g. img should be img[alt]:not([alt=""]). But other implicit roles can't be written as an css selector (yet) because they are dependent on the absence of different parents.

@@ -376,8 +376,7 @@ lookupTables.role = {
one: ['rowgroup', 'row']
},
nameFrom: ['author'],
context: null,
implicit: ['table']
context: null
Copy link
Author

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.

Copy link
Contributor

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?

Copy link
Author

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.

// 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'))) {
Copy link
Author

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...

Copy link
Contributor

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.

@lemnis
Copy link
Author

lemnis commented Dec 14, 2017

I added a option that disables the check on implicit roles, e.g. currently <nav role='navigation'> is disallowed at default. This setting will probably be changed often by the user, because the mentioned html is often recommended for better browser support.


@WilcoFiers

What is opinion about the current setup within the axe.commons.aria ?

Have you any preference how to setup beneath mention cases:

  • Elements with an implicit role that can only be calculated trough javascript:
  • Elements who need some javascript to check if a specific role is allowed.

@WilcoFiers
Copy link
Contributor

@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 grunt test-fast, or if you want to run them individually in the browser, you can use grunt dev.

* @param {Element} node element
* @return {String} normalized tagName
*/
axe.utils.getTagName = function(node) {
Copy link
Contributor

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.

Copy link
Author

@lemnis lemnis Jan 10, 2018

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.

Copy link
Contributor

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?

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));
Copy link
Contributor

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.

aria.getRoles = function(node) {
var roles = [];
if(node.hasAttribute('role')){
roles = roles.concat(node.getAttribute('role').split(' '));
Copy link
Contributor

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.

return true;
}

roles.forEach(function(role) {
Copy link
Contributor

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.

// 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'))) {
Copy link
Contributor

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.

var disallowedRoles = [];

// check if check on the element is disabled through the options
if (options.disable && options.disable.indexOf(tagName) > -1) {
Copy link
Contributor

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);
Copy link
Contributor

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
Copy link
Contributor

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']
Copy link
Contributor

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?

Copy link
Author

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);
Copy link
Contributor

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.

@CLAassistant
Copy link

CLAassistant commented Mar 25, 2018

CLA assistant check
Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ lemnis
❌ lemnisSP
You have signed the CLA already but the status is still pending? Let us recheck it.

@jeeyyy jeeyyy self-assigned this Jun 4, 2018
@jeeyyy jeeyyy changed the title WIP, new rule: aria-allowed-role feat(new rule): aria-allowed-role Jun 6, 2018
@jeeyyy
Copy link
Contributor

jeeyyy commented Jun 7, 2018

@lemnis @lemnisSP
Thanks for all the work on this.
I had forked this into a different PR - #945

Hence closing this.

@jeeyyy jeeyyy closed this Jun 7, 2018
@stephenmathieson stephenmathieson mentioned this pull request Jun 28, 2018
7 tasks
mrtnvh pushed a commit to mrtnvh/axe-core that referenced this pull request Nov 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants