Skip to content

Commit

Permalink
Merge branch 'shadowDOM' into sd/table-rules
Browse files Browse the repository at this point in the history
# Conflicts:
#	lib/commons/dom/has-content.js
#	test/commons/dom/has-content.js
  • Loading branch information
WilcoFiers committed Jul 11, 2017
2 parents 480141b + 5c2802b commit 790ae27
Show file tree
Hide file tree
Showing 20 changed files with 241 additions and 47 deletions.
8 changes: 8 additions & 0 deletions .retireignore.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@
{
"path": "node_modules/request",
"justification" : "Used only for testing"
},
{
"path": "node_modules/mocha",
"justification": "Used only for testing"
},
{
"path": "node_modules/growl",
"justification": "Temporary disabling until vuln is fixed"
}
]
13 changes: 7 additions & 6 deletions lib/checks/visibility/hidden-content.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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(node)) {
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
5 changes: 1 addition & 4 deletions lib/commons/dom/find-up.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ dom.findUp = function (element, target) {
return null;
}

parent = (element.assignedSlot) ? element.assignedSlot : element.parentNode;
if (parent.nodeType === 11) {
parent = parent.host;
}
// recursively walk up the DOM, checking each parent node
parent = dom.getComposedParent(element);
while (parent && matches.indexOf(parent) === -1) {
parent = (parent.assignedSlot) ? parent.assignedSlot : parent.parentNode;
if (parent && parent.nodeType === 11) {
Expand Down
19 changes: 19 additions & 0 deletions lib/commons/dom/get-composed-parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*global dom */
/**
* Get an element's parent in the composed tree
* @param DOMNode Element
* @return DOMNode Parent element
*/
dom.getComposedParent = function getComposedParent (element) {
if (element.assignedSlot) {
return element.assignedSlot; // content of a shadow DOM slot
} else if (element.parentNode) {
var parentNode = element.parentNode;
if (parentNode.nodeType === 1) {
return parentNode; // Regular node
} else if (parentNode.host) {
return parentNode.host; // Shadow root
}
}
return null; // Root node
};
2 changes: 0 additions & 2 deletions lib/commons/dom/is-visible.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,9 @@ dom.isVisible = function (el, screenReader, recursed) {
}

parent = (el.assignedSlot) ? el.assignedSlot : el.parentNode;

if (parent) {
return dom.isVisible(parent, screenReader, true);
}

return false;

};
65 changes: 50 additions & 15 deletions test/checks/visibility/hidden-content.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,81 @@
/* global xit */
describe('hidden content', function () {
'use strict';

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

var fixture = document.getElementById('fixture');
var shadowSupport = axe.testUtils.shadowSupport.v1;
var checkContext = {
_data: null,
data: function (d) {
this._data = d;
}
};

function checkSetup (html, options, target) {
fixture.innerHTML = html;
axe._tree = axe.utils.getFlattenedTree(fixture);
var node = fixture.querySelector(target || '#target');
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node);
return [node, options, virtualNode];
}

afterEach(function () {
fixture.innerHTML = '';
checkContext._data = null;
axe._tree = undefined;
});

it('should return undefined with display:none and children', function () {
fixture.innerHTML = '<div id="target" style="display: none;"><p>Some paragraph text.</p></div>';
var node = fixture.querySelector('#target');
assert.isUndefined(checks['hidden-content'].evaluate.call(checkContext, node));
var params = checkSetup('<div id="target" style="display: none;"><p>Some paragraph text.</p></div>');
assert.isUndefined(checks['hidden-content'].evaluate.apply(checkContext, params));
});

it('should return undefined with visibility:hidden and children', function () {
fixture.innerHTML = '<div id="target" style="visibility: hidden;"><p>Some paragraph text.</p></div>';
var node = fixture.querySelector('#target');
assert.isUndefined(checks['hidden-content'].evaluate.call(checkContext, node));
var params = checkSetup('<div id="target" style="visibility: hidden;"><p>Some paragraph text.</p></div>');
assert.isUndefined(checks['hidden-content'].evaluate.apply(checkContext, params));
});

it('should return true with visibility:hidden and parent with visibility:hidden', function () {
fixture.innerHTML = '<div style="visibility: hidden;"><p id="target" style="visibility: hidden;">Some paragraph text.</p></div>';
var node = fixture.querySelector('#target');
assert.isTrue(checks['hidden-content'].evaluate.call(checkContext, node));
var params = checkSetup('<div style="visibility: hidden;"><p id="target" style="visibility: hidden;">Some paragraph text.</p></div>');
assert.isTrue(checks['hidden-content'].evaluate.apply(checkContext, params));
});

it('should return true with aria-hidden and no content', function () {
fixture.innerHTML = '<span id="target" class="icon" aria-hidden="true"></span>';
var node = fixture.querySelector('#target');
assert.isTrue(checks['hidden-content'].evaluate.call(checkContext, node));
var params = checkSetup('<span id="target" class="icon" aria-hidden="true"></span>');
assert.isTrue(checks['hidden-content'].evaluate.apply(checkContext, params));
});

it('should skip whitelisted elements', function () {
var node = document.querySelector('head');
assert.isTrue(checks['hidden-content'].evaluate.call(checkContext, node));
axe._tree = axe.utils.getFlattenedTree(document.documentElement);
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node);
assert.isTrue(checks['hidden-content'].evaluate(node, undefined, virtualNode));
});

(shadowSupport ? it : xit)('works on elements in a shadow DOM', function () {
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);

var shadow = document.querySelector('#shadow');
var virtualShadow = axe.utils.getNodeFromTree(axe._tree[0], 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)
);
});
});
4 changes: 3 additions & 1 deletion test/commons/aria/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ describe('aria.validateAttrValue', function () {
var orig = axe.commons.aria._lut.attributes,
fixture = document.getElementById('fixture');

var shadowSupport = axe.testUtils.shadowSupport;

afterEach(function () {
axe.commons.aria._lut.attributes = orig;
fixture.innerHTML = '';
Expand Down Expand Up @@ -213,7 +215,7 @@ describe('aria.validateAttrValue', function () {
it('should work in shadow DOM', function () {
var shadEl;

if (document.body && typeof document.body.attachShadow === 'function') {
if (shadowSupport.v1) {
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
fixture.innerHTML = '<div></div>';
Expand Down
5 changes: 3 additions & 2 deletions test/commons/dom/find-up.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ describe('dom.findUp', function () {
'use strict';

var fixture = document.getElementById('fixture');
var shadowSupport = axe.testUtils.shadowSupport;

afterEach(function () {
fixture.innerHTML = '';
Expand Down Expand Up @@ -47,7 +48,7 @@ describe('dom.findUp', function () {
root.appendChild(div);
div.appendChild(createContentSlotted());
}
if (document.body && typeof document.body.attachShadow === 'function') {
if (shadowSupport.v1) {
fixture.innerHTML = '<label><div><p><a>hello</a></p></div></label>';
makeShadowTree(fixture.querySelector('div'));
var tree = axe.utils.getFlattenedTree(fixture.firstChild);
Expand All @@ -68,7 +69,7 @@ describe('dom.findUp', function () {
root.appendChild(div);
div.appendChild(createContent());
}
if (document.body && typeof document.body.attachShadow === 'function') {
if (shadowSupport.v1) {
fixture.innerHTML = '<label><div></div></label>';
makeShadowTree(fixture.querySelector('div'));
var tree = axe.utils.getFlattenedTree(fixture.firstChild);
Expand Down
72 changes: 72 additions & 0 deletions test/commons/dom/get-composed-parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* global xit */
describe('dom.getComposedParent', function () {
'use strict';
var getComposedParent = axe.commons.dom.getComposedParent;
var fixture = document.getElementById('fixture');
var shadowSupport = axe.testUtils.shadowSupport.v1;

afterEach(function () {
fixture.innerHTML = '';
});

it('returns the parentNode normally', function () {
fixture.innerHTML = '<div id="parent"><div id="target"></div></div>';

var actual = getComposedParent(document.getElementById('target'));
assert.instanceOf(actual, Node);
assert.equal(actual, document.getElementById('parent'));
});

it('returns null from the documentElement', function () {
assert.isNull(
getComposedParent(document.documentElement)
);
});

(shadowSupport ? it : xit)('returns the slot node for slotted content', function () {
fixture.innerHTML = '<div id="shadow"><div id="target"></div></div>';
var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="grand-parent">' +
'<slot id="parent"></slot>' +
'</div>';

var actual = getComposedParent(fixture.querySelector('#target'));
assert.instanceOf(actual, Node);
assert.equal(actual, shadowRoot.querySelector('#parent'));
});

(shadowSupport ? it : xit)('returns explicitly slotted nodes', function () {
fixture.innerHTML = '<div id="shadow"><div id="target" slot="bar"></div></div>';
var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="grand-parent">' +
'<slot name="foo"></slot>' +
'<slot id="parent" name="bar"></slot>' +
'</div>';

var actual = getComposedParent(fixture.querySelector('#target'));
assert.instanceOf(actual, Node);
assert.equal(actual, shadowRoot.querySelector('#parent'));
});

(shadowSupport ? it : xit)('returns elements within a shadow tree', function () {
fixture.innerHTML = '<div id="shadow"> content </div>';
var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="parent">' +
'<slot id="target"></slot>' +
'</div>';

var actual = getComposedParent(shadowRoot.querySelector('#target'));
assert.instanceOf(actual, Node);
assert.equal(actual, shadowRoot.querySelector('#parent'));
});

(shadowSupport ? it : xit)('returns the host when it reaches the shadow root', function () {
fixture.innerHTML = '<div id="parent"> content </div>';
var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="target"> <slot></slot> </div>';

var actual = getComposedParent(shadowRoot.querySelector('#target'));
assert.instanceOf(actual, Node);
assert.equal(actual, fixture.querySelector('#parent'));
});
});
3 changes: 2 additions & 1 deletion test/commons/dom/get-root-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('dom.getRootNode', function () {
'use strict';

var fixture = document.getElementById('fixture');
var shadowSupported = axe.testUtils.shadowSupport.v1;

afterEach(function () {
fixture.innerHTML = '';
Expand All @@ -27,7 +28,7 @@ describe('dom.getRootNode', function () {
it('should return the shadow root when it is inside the shadow DOM', function () {
var shadEl;

if (document.body && typeof document.body.attachShadow === 'function') {
if (shadowSupported) {
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
fixture.innerHTML = '<div></div>';
Expand Down
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 = axe.testUtils.shadowSupport.v1;
var tree;

it('returns false if there is no content', function () {
Expand Down Expand Up @@ -66,4 +68,36 @@ describe('dom.hasContent', function () {
);
axe._tree = null;
});

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])
);
});
});
5 changes: 3 additions & 2 deletions test/commons/dom/idrefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('dom.idrefs', function () {
'use strict';

var fixture = document.getElementById('fixture');
var shadowSupported = axe.testUtils.shadowSupport.v1;

afterEach(function () {
fixture.innerHTML = '';
Expand All @@ -37,7 +38,7 @@ describe('dom.idrefs', function () {
});

it('should find only referenced nodes within the current root: shadow DOM', function () {
if (document.body && typeof document.body.attachShadow === 'function') {
if (shadowSupported) {
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
fixture.innerHTML = '<div target="target"><div id="target"></div></div>';
Expand All @@ -51,7 +52,7 @@ describe('dom.idrefs', function () {
});

it('should find only referenced nodes within the current root: document', function () {
if (document.body && typeof document.body.attachShadow === 'function') {
if (shadowSupported) {
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
fixture.innerHTML = '<div target="target" class="parent"><div id="target"></div></div>';
Expand Down
Loading

0 comments on commit 790ae27

Please sign in to comment.