-
Notifications
You must be signed in to change notification settings - Fork 780
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
shadowDOM accessible text calculation #420
Conversation
@@ -0,0 +1,26 @@ | |||
/* global axe, dom */ | |||
/** | |||
* Find a elements reference from a given context |
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.
sp: referenced
lib/commons/text/accessible-text.js
Outdated
function checkDescendant(element, nodeName) { | ||
var candidate = element.querySelector(nodeName.toLowerCase()); | ||
function checkDescendant({ actualNode }, nodeName) { | ||
var candidate = actualNode.querySelector(nodeName.toLowerCase()); |
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.
should we put a note in here to indicate that this function is intended to be used only on elements that cannot contain a shadow root?
var encounteredNodes = []; | ||
let accessibleNameComputation; | ||
const encounteredNodes = []; | ||
if (element instanceof 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.
This works differently from a lot of the other commons - which do not try to maintain backwards compatibility. Whether we want to support backwards compatibility or not and if we do, we need to change those other utilities.
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.
The reason I did this is because it would have taken a whole lot of additional changes to make sure every method that uses accessibleText has access to a virtual node. It wasn't even for backward compatibility, but that's a good additional reason. Further, I think these methods should be useful outside of a rule too, which they aren't currently, since they rely on setup that gets done in another place.
I think, instead of calling element = axe.utils.getNodeFromTree(axe._tree[0], element);
we need a method that will take any DOM element and return it's virtual counterpart. Something like virtualNode = axe.utils.getVirtualNode(node)
. It can look at axe._tree[0]
if available, and if it isn't, it should create a new tree and pull the virtual node from that. We could also make that method store virtual nodes in a set rather than a tree, so that lookup is faster.
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.
Yeah, the biggest problem with not passing in the virtualNode is the potential performance impact. I am worried about memory leaks if we use these APIs (or make them usable) without understanding what they do. So I don't support building this cache if it doesn't exist.
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 am going to accept this pull request and have opened a separate issue to deal with this #435
lib/commons/text/accessible-text.js
Outdated
returnText += ' '; | ||
} | ||
returnText += accessibleNameComputation(nodes[i], inLabelledByContext, inControlContext); | ||
returnText += accessibleNameComputation( |
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 breaks our current style conventions
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.
Ugh... It's really frustrating going between axe-core and axe-devtools. :/ sorry
var node = fixture.querySelector('#target'); | ||
assert.isFalse(checks.fieldset.evaluate.call(checkContext, 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.
We need to add tests that deal with this sort of thing
<div>
<form>
<div class="fieldset">
<span slot="legend">Shadow DOM fieldset</span>
<label>Option 1<input type="radio" value="one" name="group" /></label>
<label>Option 2<input type="radio" value="two" name="group" /></label>
</div>
</form>
</div>
<script>
function createContentSlotted() {
var group = document.createElement('fieldset');
group.innerHTML = '<legend><slot name="legend"></slot></legend><slot></slot>';
return group;
}
function makeShadowTreeSlotted(node) {
var root = node.attachShadow({mode: 'open'});
root.appendChild(createContentSlotted());
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.fieldset').forEach(makeShadowTreeSlotted);
});
</script>
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.
Should be covered in #425
var node = fixture.querySelector('span'); | ||
assert.isTrue(checks['focusable-no-name'].evaluate(node)); | ||
}); | ||
|
||
it('should pass if the element is tabbable but has an accessible name', function () { | ||
fixture.innerHTML = '<a href="#" title="Hello"></a>'; | ||
fixtureSetup('<a href="#" title="Hello"></a>'); | ||
var node = fixture.querySelector('a'); | ||
assert.isFalse(checks['focusable-no-name'].evaluate(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.
Add a test where when an anchor has content inside the shadow root, it returns false. Test this for slotted content as well as fallback content inside the default slot
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.
@WilcoFiers Can you add one more
var node = document.createElement('a');
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<slot>Fallback content</slot>';
fixtureSetup(node);
assert.isFalse(checks['focusable-no-name'].evaluate(node));
assert.equal(axe.commons.text.accessibleText(target), ''); | ||
}); | ||
|
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 we should add a couple more tests here;
- fallback content for v1
- correct use of an attribute (e.g. title) within the slotted shadow DOM content,
- correct use of an attribute (e.g. title) within the shadow DOM content
var node = fixture.querySelector('#target'); | ||
assert.isTrue(checks['implicit-label'].evaluate(node)); | ||
}); | ||
|
||
it('should return false if a label is not present', function () { | ||
var node = document.createElement('input'); | ||
node.type = 'text'; | ||
fixture.appendChild(node); | ||
fixtureSetup(node); | ||
|
||
assert.isFalse(checks['implicit-label'].evaluate(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.
This needs to support this example
<form>
<label>Option 1 <span class="slotted"><input type="text" name="slottedLabel" /></span></label>
\ </form>
<script>
function createContentSlotted() {
var group = document.createElement('span');
group.innerHTML = '<slot></slot>';
return group;
}
function makeShadowTreeSlotted(node) {
var root = node.attachShadow({mode: 'open'});
root.appendChild(createContentSlotted());
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.slotted').forEach(makeShadowTreeSlotted);
});
</script>
Which works properly in both Safari and Chrome with VO
@@ -43,8 +43,8 @@ describe('fieldset', function () { | |||
}); | |||
|
|||
it('should return false if the group has no legend element', function () { | |||
fixture.innerHTML = '<fieldset><input type="' + type + '" id="target" name="uniqueyname">' + | |||
'<input type="' + type + '" name="uniqueyname"></fieldset>'; | |||
fixtureSetup('<fieldset><input type="' + type + '" id="target" name="uniqueyname">' + |
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 test here should work when the fieldset has been found by going up the slotted content - see the example below
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.
Should be covered in #425
var node = fixture.querySelector('table'); | ||
|
||
assert.isFalse(checks['same-caption-summary'].evaluate(node)); | ||
|
||
}); | ||
|
||
it('should return true if summary and caption are the same', function () { | ||
fixture.innerHTML = '<table summary="Hi"><caption>Hi</caption><tr><td></td></tr></table>'; | ||
fixtureSetup('<table summary="Hi"><caption>Hi</caption><tr><td></td></tr></table>'); | ||
var node = fixture.querySelector('table'); | ||
|
||
assert.isTrue(checks['same-caption-summary'].evaluate(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.
This should catch the following situation
<div class="table">
<span slot="caption">Caption</span>
<span slot="one">Data element 1</span>
<span slot="two">Data element 2</span>
</div>
<script>
function createContentTable() {
var group = document.createElement('table');
group.innerHTML = '<caption><slot name="caption"></slot></caption>' +
'<tr><td><slot name="one"></slot></td><td><slot name="two"></slot></td></tr>';
group.setAttribute('summary', 'Caption');
return group;
}
function makeShadowTreeTable(node) {
var root = node.attachShadow({mode: 'open'});
root.appendChild(createContentTable());
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.table').forEach(makeShadowTreeTable);
});
</script>
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.
please address the comments
test/commons/text/accessible-text.js
Outdated
assert.equal(axe.commons.text.accessibleText(rule2a), 'Beep'); | ||
// var rule2a = axe.utils.querySelectorAll(axe._tree, '#beep')[0]; | ||
var rule2b = axe.utils.querySelectorAll(axe._tree, '#flash')[0]; | ||
// assert.equal(axe.commons.text.accessibleText(rule2a), 'Beep'); |
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.
why is this commented?
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.
oops.
c37d993
to
55d6aa1
Compare
# Conflicts: # lib/commons/dom/find-elms-in-context.js # lib/commons/text/accessible-text.js # test/checks/keyboard/focusable-no-name.js # test/checks/tables/same-caption-summary.js # test/commons/text/accessible-text.js
@dylanb Thanks for all the excellent comments. I've updated the PR accordingly. Can you review again? |
(shadowSupport.v1 ? it : xit)('should match slotted caption elements', function () { | ||
var node = document.createElement('div'); | ||
node.innerHTML = '<span slot="caption">Caption</span>' + | ||
'<span slot="one">Data element 1</span>' + |
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.
@WilcoFiers indentation
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's an indentation feature ;)
@WilcoFiers one small indentation problem and then I suggested another test - then we should be good to go |
# Conflicts: # test/testutils.js
This PR aims to close #399.