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

shadowDOM accessible text calculation #420

Merged
merged 7 commits into from
Jul 17, 2017
Merged

Conversation

WilcoFiers
Copy link
Contributor

This PR aims to close #399.

@WilcoFiers WilcoFiers changed the base branch from master to shadowDOM July 12, 2017 12:12
@@ -0,0 +1,26 @@
/* global axe, dom */
/**
* Find a elements reference from a given context
Copy link
Contributor

@dylanb dylanb Jul 12, 2017

Choose a reason for hiding this comment

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

sp: referenced

function checkDescendant(element, nodeName) {
var candidate = element.querySelector(nodeName.toLowerCase());
function checkDescendant({ actualNode }, nodeName) {
var candidate = actualNode.querySelector(nodeName.toLowerCase());
Copy link
Contributor

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

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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

returnText += ' ';
}
returnText += accessibleNameComputation(nodes[i], inLabelledByContext, inControlContext);
returnText += accessibleNameComputation(
Copy link
Contributor

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

Copy link
Contributor Author

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));

Copy link
Contributor

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>

Copy link
Contributor Author

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

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

Copy link
Contributor

@dylanb dylanb Jul 14, 2017

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), '');
});

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 we should add a couple more tests here;

  1. fallback content for v1
  2. correct use of an attribute (e.g. title) within the slotted shadow DOM content,
  3. 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));
});
Copy link
Contributor

@dylanb dylanb Jul 12, 2017

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

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

Copy link
Contributor Author

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));
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 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>

Copy link
Contributor

@dylanb dylanb left a 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

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

Choose a reason for hiding this comment

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

why is this commented?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops.

# 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
@WilcoFiers
Copy link
Contributor Author

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

Choose a reason for hiding this comment

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

@WilcoFiers indentation

Copy link
Contributor Author

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 ;)

@dylanb
Copy link
Contributor

dylanb commented Jul 14, 2017

@WilcoFiers one small indentation problem and then I suggested another test - then we should be good to go

@WilcoFiers WilcoFiers merged commit 414fcbe into shadowDOM Jul 17, 2017
@dylanb dylanb deleted the sd/accessible-text branch July 17, 2017 13:57
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.

2 participants