Skip to content

Commit

Permalink
feat: Allow hidden-content to work through shadow DOM bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Jul 5, 2017
1 parent a76db4c commit 789d62e
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 28 deletions.
15 changes: 9 additions & 6 deletions lib/checks/visibility/hidden-content.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
let styles = window.getComputedStyle(node);
const whitelist = ['SCRIPT', 'HEAD', 'TITLE', 'NOSCRIPT', 'STYLE', 'TEMPLATE'];
if (
!whitelist.includes(node.tagName.toUpperCase()) &&
axe.commons.dom.hasContent(virtualNode)
) {

let whitelist = ['SCRIPT', 'HEAD', 'TITLE', 'NOSCRIPT', 'STYLE', 'TEMPLATE'];
if (!whitelist.includes(node.tagName.toUpperCase()) && axe.commons.dom.hasContent(virtualNode)) {
const styles = window.getComputedStyle(node);
if (styles.getPropertyValue('display') === 'none') {
return undefined;
} else if (styles.getPropertyValue('visibility') === 'hidden') {
if (node.parentNode) {
var parentStyle = window.getComputedStyle(node.parentNode);
}
// Check if visibility isn't inherited
const parent = axe.commons.dom.getComposedParent(node);
const parentStyle = parent && window.getComputedStyle(parent);
if (!parentStyle || parentStyle.getPropertyValue('visibility') !== 'hidden') {
return undefined;
}
Expand Down
51 changes: 33 additions & 18 deletions lib/commons/dom/has-content.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
/*global dom, aria, axe */
/*global dom, aria */
const hiddenTextElms = [
'HEAD', 'TITLE', 'TEMPLATE', 'SCRIPT','STYLE',
'IFRAME', 'OBJECT', 'VIDEO', 'AUDIO', 'NOSCRIPT'
];

function hasChildTextNodes (elm) {
if (!hiddenTextElms.includes(elm.actualNode.nodeName.toUpperCase())) {
return elm.children.some(({ actualNode }) => (
actualNode.nodeType === 3 && actualNode.nodeValue.trim()
));
}
}

/**
* Check that the element has visible content
* in the form of either text, an aria-label or visual content such as image
*
* @param {Object} virtual DOM node
* @return boolean
*/
dom.hasContent = function hasContent(elm) {
if (
elm.actualNode.textContent.trim() ||
dom.hasContent = function hasContent (elm) {
/* global console */
console.log(
elm.actualNode,
hasChildTextNodes(elm),
dom.isVisualContent(elm.actualNode),
aria.label(elm)
) {
return true;
}

const contentElms = axe.utils.querySelectorAll(elm, '*');
for (let i = 0; i < contentElms.length; i++) {
if (
aria.label(contentElms[i]) ||
dom.isVisualContent(contentElms[i].actualNode)
) {
return true;
}
}
return false;
);
return (
// It has text
hasChildTextNodes(elm) ||
// It is a graphical element
dom.isVisualContent(elm.actualNode) ||
// It has an ARIA label
!!aria.label(elm) ||
// or one of it's descendants does
elm.children.some(child => (
child.actualNode.nodeType === 1 && dom.hasContent(child)
))
);
};
3 changes: 1 addition & 2 deletions lib/commons/dom/is-visible.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ dom.isVisible = function (el, screenReader, recursed) {
return false;
}

parent = dom.getComposedParent(el);
parent = (el.assignedSlot) ? el.assignedSlot : el.parentNode;
if (parent) {
return dom.isVisible(parent, screenReader, true);
}

return false;

};
35 changes: 33 additions & 2 deletions test/checks/visibility/hidden-content.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* global xit */
describe('hidden content', function () {
'use strict';

var fixture = document.getElementById('fixture');

var shadowSupport = document.body && typeof document.body.attachShadow === 'function';
var checkContext = {
_data: null,
data: function (d) {
Expand Down Expand Up @@ -48,6 +49,36 @@ describe('hidden content', function () {
var node = document.querySelector('head');
axe._tree = axe.utils.getFlattenedTree(document.documentElement);
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node);
assert.isTrue(checks['hidden-content'].evaluate.call(checkContext, node, undefined, virtualNode));
assert.isTrue(checks['hidden-content'].evaluate(node, undefined, virtualNode));
});

(shadowSupport ? it : xit)('works on elements in a shadow DOM', function () {
/* global console */
fixture.innerHTML = '<div id="shadow"> <div id="content">text</div> </div>';
var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="target" style="display:none">' +
'<slot></slot>' +
'</div>';
axe._tree = axe.utils.getFlattenedTree(fixture);
console.log(axe._tree);

var shadow = document.querySelector('#shadow');
var virtualShadow = axe.utils.getNodeFromTree(axe._tree[0], shadow);
console.log(virtualShadow, shadow);
assert.isTrue(
checks['hidden-content'].evaluate(shadow, undefined, virtualShadow)
);

var target = shadowRoot.querySelector('#target');
var virtualTarget = axe.utils.getNodeFromTree(axe._tree[0], target);
assert.isUndefined(
checks['hidden-content'].evaluate(target, undefined, virtualTarget)
);

var content = document.querySelector('#content');
var virtualContent = axe.utils.getNodeFromTree(axe._tree[0], content);
assert.isTrue(
checks['hidden-content'].evaluate(content, undefined, virtualContent)
);
});
});
34 changes: 34 additions & 0 deletions test/commons/dom/has-content.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* global xit */
describe('dom.hasContent', function () {
'use strict';
var hasContent = axe.commons.dom.hasContent;
var fixture = document.getElementById('fixture');
var shadowSupport = document.body && typeof document.body.attachShadow === 'function';
var tree;

it('returns false if there is no content', function () {
Expand Down Expand Up @@ -51,4 +53,36 @@ describe('dom.hasContent', function () {
hasContent(axe.utils.querySelectorAll(tree, '#target')[0])
);
});

it('is false if the element does not show text', function () {
fixture.innerHTML = '<style id="target"> #foo { color: green } </style>';
tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(
hasContent(axe.utils.querySelectorAll(tree, '#target')[0])
);
});

(shadowSupport ? it : xit)('looks at content of shadow dom elements', function () {
fixture.innerHTML = '<div id="target"></div>';
var shadow = fixture.querySelector('#target').attachShadow({ mode: 'open' });
shadow.innerHTML = 'Some text';
tree = axe.utils.getFlattenedTree(fixture);

assert.isTrue(
hasContent(axe.utils.querySelectorAll(tree, '#target')[0])
);
});

(shadowSupport ? it : xit)('looks at the slots in a shadow tree', function () {
fixture.innerHTML = '<div id="shadow">some text</div>';
var shadow = fixture.querySelector('#shadow').attachShadow({ mode: 'open' });
shadow.innerHTML = '<div class="target"><slot></slot></div>';
tree = axe.utils.getFlattenedTree(fixture);
var node = axe.utils.querySelectorAll(tree, '.target');

axe.log(tree, node);
assert.isTrue(
hasContent(axe.utils.querySelectorAll(tree, '.target')[0])
);
});
});

0 comments on commit 789d62e

Please sign in to comment.