diff --git a/lib/checks/.DS_Store b/lib/checks/.DS_Store new file mode 100644 index 0000000000..6a583cd791 Binary files /dev/null and b/lib/checks/.DS_Store differ diff --git a/lib/core/utils/flattened-tree.js b/lib/core/utils/flattened-tree.js index 0c5189f325..ec618bbcb1 100644 --- a/lib/core/utils/flattened-tree.js +++ b/lib/core/utils/flattened-tree.js @@ -51,6 +51,20 @@ function getSlotChildren(node) { return retVal; } +const possibleShadowRoots = ['article', 'aside', 'blockquote', + 'body', 'div', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', + 'header', 'main', 'nav', 'p', 'section', 'span']; +axe.utils.isShadowRoot = function isShadowRoot (node) { + const nodeName = node.nodeName.toLowerCase(); + if (node.shadowRoot) { + if (/^[a-z][a-z0-9_.-]*-[a-z0-9_.-]*$/.test(nodeName) || + possibleShadowRoots.includes(nodeName)) { + return true; + } + } + return false; +}; + /** * Recursvely returns an array of the virtual DOM nodes at this level * excluding comment nodes and the shadow DOM nodes and @@ -75,8 +89,8 @@ axe.utils.getFlattenedTree = function (node, shadowId) { node = node.documentElement; } nodeName = node.nodeName.toLowerCase(); - // for some reason Chrome's marquee element has an open shadow DOM - if (node.shadowRoot && nodeName !== 'marquee') { + + if (axe.utils.isShadowRoot(node)) { // generate an ID for this shadow root and overwrite the current // closure shadowId with this value so that it cascades down the tree retVal = virtualDOMfromNode(node, shadowId); diff --git a/test/core/utils/flattened-tree.js b/test/core/utils/flattened-tree.js index 279a4af8a6..4d29057588 100644 --- a/test/core/utils/flattened-tree.js +++ b/test/core/utils/flattened-tree.js @@ -66,6 +66,32 @@ function shadowIdAssertions () { } +describe('isShadowRoot', function () { + 'use strict'; + var isShadowRoot = axe.utils.isShadowRoot; + + it('returns false if the node has no shadowRoot', function () { + assert.isFalse(isShadowRoot({ nodeName: 'DIV', shadowRoot: undefined })); + }); + it('returns true if the native element allows shadow DOM', function () { + assert.isTrue(isShadowRoot({ nodeName: 'DIV', shadowRoot: {} })); + assert.isTrue(isShadowRoot({ nodeName: 'H1', shadowRoot: {} })); + assert.isTrue(isShadowRoot({ nodeName: 'ASIDE', shadowRoot: {} })); + }); + it('returns true if a custom element with shadowRoot', function () { + assert.isTrue(isShadowRoot({ nodeName: 'X-BUTTON', shadowRoot: {} })); + assert.isTrue(isShadowRoot({ nodeName: 'T1000-SCHWARZENEGGER', shadowRoot: {} })); + }); + it('returns true if an invalid custom element with shadowRoot', function () { + assert.isFalse(isShadowRoot({ nodeName: '0-BUZZ', shadowRoot: {} })); + assert.isFalse(isShadowRoot({ nodeName: '--ELM--', shadowRoot: {} })); + }); + it('returns false if the native element does not allow shadow DOM', function () { + assert.isFalse(isShadowRoot({ nodeName: 'IFRAME', shadowRoot: {} })); + assert.isFalse(isShadowRoot({ nodeName: 'STRONG', shadowRoot: {} })); + }); +}); + if (shadowSupport.v0) { describe('flattened-tree shadow DOM v0', function () { 'use strict'; @@ -154,6 +180,26 @@ if (shadowSupport.v1) { assert.isTrue(virtualDOM[0].children[2].children[1].children[0].children[0].actualNode.textContent === 'fallback content'); assert.isTrue(virtualDOM[0].children[2].children[1].children[0].children[1].actualNode.nodeName === 'LI'); }); + it('calls isShadowRoot to identify a shadow root', function () { + var isShadowRoot = axe.utils.isShadowRoot; + fixture.innerHTML = '
'; + var div = fixture.querySelector('div'); + var shadowRoot = div.attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = '

Just a man in the back

'; + + // Test without isShqdowRoot overwritten + assert.equal(axe.utils.getFlattenedTree(div)[0].children.length, 1); + + var called = false; + axe.utils.isShadowRoot = function () { + called = true; + return false; + }; + // Test with isShadowRoot overwritten + assert.equal(axe.utils.getFlattenedTree(div)[0].children.length, 0); + assert.isTrue(called); + axe.utils.isShadowRoot = isShadowRoot; + }); }); describe('flattened-tree shadow DOM v1: boxed slots', function () { 'use strict';