diff --git a/lib/checks/visibility/hidden-content.js b/lib/checks/visibility/hidden-content.js index 3daef0a360..13358e3aa0 100644 --- a/lib/checks/visibility/hidden-content.js +++ b/lib/checks/visibility/hidden-content.js @@ -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; } diff --git a/lib/commons/dom/has-content.js b/lib/commons/dom/has-content.js index 5581018d19..b70fee41dc 100644 --- a/lib/commons/dom/has-content.js +++ b/lib/commons/dom/has-content.js @@ -1,4 +1,17 @@ -/*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 @@ -6,22 +19,24 @@ * @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) + )) + ); }; diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js index 433fdc25b3..6318e2b8e8 100644 --- a/lib/commons/dom/is-visible.js +++ b/lib/commons/dom/is-visible.js @@ -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; - }; diff --git a/test/checks/visibility/hidden-content.js b/test/checks/visibility/hidden-content.js index 89c04d7070..22fde18476 100644 --- a/test/checks/visibility/hidden-content.js +++ b/test/checks/visibility/hidden-content.js @@ -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) { @@ -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 = '