diff --git a/.eslintrc.js b/.eslintrc.js
index 71a39bc1ca..37592b6609 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -59,13 +59,37 @@ module.exports = {
selector: 'MemberExpression[property.name=tagName]',
message: "Don't use node.tagName, use node.nodeName instead."
},
+ // node.attributes can be clobbered so is unsafe to use
+ // @see https://github.com/dequelabs/axe-core/pull/1432
{
- // node.attributes can be clobbered so is unsafe to use
- // @see https://github.com/dequelabs/axe-core/pull/1432
+ // node.attributes
selector:
'MemberExpression[object.name=node][property.name=attributes]',
message:
"Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead."
+ },
+ {
+ // vNode.actualNode.attributes
+ selector:
+ 'MemberExpression[object.property.name=actualNode][property.name=attributes]',
+ message:
+ "Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead."
+ },
+ // node.contains doesn't work with shadow dom
+ // @see https://github.com/dequelabs/axe-core/issues/4194
+ {
+ // node.contains()
+ selector:
+ 'CallExpression[callee.object.name=node][callee.property.name=contains]',
+ message:
+ "Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead."
+ },
+ {
+ // vNode.actualNode.contains()
+ selector:
+ 'CallExpression[callee.object.property.name=actualNode][callee.property.name=contains]',
+ message:
+ "Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead."
}
]
},
diff --git a/lib/checks/mobile/target-size-evaluate.js b/lib/checks/mobile/target-size-evaluate.js
index 85561055b2..88c029f34e 100644
--- a/lib/checks/mobile/target-size-evaluate.js
+++ b/lib/checks/mobile/target-size-evaluate.js
@@ -5,6 +5,7 @@ import {
rectHasMinimumSize,
hasVisualOverlap
} from '../../commons/math';
+import { contains } from '../../core/utils';
/**
* Determine if an element has a minimum size, taking into account
@@ -187,9 +188,7 @@ function toDecimalSize(rect) {
}
function isDescendantNotInTabOrder(vAncestor, vNode) {
- return (
- vAncestor.actualNode.contains(vNode.actualNode) && !isInTabOrder(vNode)
- );
+ return contains(vAncestor, vNode) && !isInTabOrder(vNode);
}
function mapActualNodes(vNodes) {
diff --git a/lib/commons/dom/get-target-rects.js b/lib/commons/dom/get-target-rects.js
index fc5b2aabe7..a2bc2eec12 100644
--- a/lib/commons/dom/get-target-rects.js
+++ b/lib/commons/dom/get-target-rects.js
@@ -2,6 +2,7 @@ import findNearbyElms from './find-nearby-elms';
import isInTabOrder from './is-in-tab-order';
import { splitRects, hasVisualOverlap } from '../math';
import memoize from '../../core/utils/memoize';
+import { contains } from '../../core/utils';
export default memoize(getTargetRects);
@@ -32,7 +33,5 @@ function getTargetRects(vNode) {
}
function isDescendantNotInTabOrder(vAncestor, vNode) {
- return (
- vAncestor.actualNode.contains(vNode.actualNode) && !isInTabOrder(vNode)
- );
+ return contains(vAncestor, vNode) && !isInTabOrder(vNode);
}
diff --git a/lib/core/utils/contains.js b/lib/core/utils/contains.js
index dfea001e1f..6b05722c0b 100644
--- a/lib/core/utils/contains.js
+++ b/lib/core/utils/contains.js
@@ -12,8 +12,10 @@ export default function contains(vNode, otherVNode) {
!vNode.shadowId &&
!otherVNode.shadowId &&
vNode.actualNode &&
+ // eslint-disable-next-line no-restricted-syntax
typeof vNode.actualNode.contains === 'function'
) {
+ // eslint-disable-next-line no-restricted-syntax
return vNode.actualNode.contains(otherVNode.actualNode);
}
diff --git a/test/checks/mobile/target-size.js b/test/checks/mobile/target-size.js
index 688f809753..f0769c3c54 100644
--- a/test/checks/mobile/target-size.js
+++ b/test/checks/mobile/target-size.js
@@ -1,23 +1,22 @@
-describe('target-size tests', function () {
- 'use strict';
-
- var checkContext = axe.testUtils.MockCheckContext();
- var checkSetup = axe.testUtils.checkSetup;
- var shadowCheckSetup = axe.testUtils.shadowCheckSetup;
- var check = checks['target-size'];
+describe('target-size tests', () => {
+ const checkContext = axe.testUtils.MockCheckContext();
+ const checkSetup = axe.testUtils.checkSetup;
+ const shadowCheckSetup = axe.testUtils.shadowCheckSetup;
+ const check = checks['target-size'];
+ const fixture = document.querySelector('#fixture');
function elmIds(elms) {
- return Array.from(elms).map(function (elm) {
+ return Array.from(elms).map(elm => {
return '#' + elm.id;
});
}
- afterEach(function () {
+ afterEach(() => {
checkContext.reset();
});
- it('returns false for targets smaller than minSize', function () {
- var checkArgs = checkSetup(
+ it('returns false for targets smaller than minSize', () => {
+ const checkArgs = checkSetup(
''
@@ -30,8 +29,8 @@ describe('target-size tests', function () {
});
});
- it('returns undefined for non-tabbable targets smaller than minSize', function () {
- var checkArgs = checkSetup(
+ it('returns undefined for non-tabbable targets smaller than minSize', () => {
+ const checkArgs = checkSetup(
''
@@ -44,8 +43,8 @@ describe('target-size tests', function () {
});
});
- it('returns true for unobscured targets larger than minSize', function () {
- var checkArgs = checkSetup(
+ it('returns true for unobscured targets larger than minSize', () => {
+ const checkArgs = checkSetup(
''
@@ -58,8 +57,8 @@ describe('target-size tests', function () {
});
});
- it('returns true for very large targets', function () {
- var checkArgs = checkSetup(
+ it('returns true for very large targets', () => {
+ const checkArgs = checkSetup(
''
@@ -68,9 +67,9 @@ describe('target-size tests', function () {
assert.deepEqual(checkContext._data, { messageKey: 'large', minSize: 24 });
});
- describe('when fully obscured', function () {
- it('returns true, regardless of size', function () {
- var checkArgs = checkSetup(
+ describe('when fully obscured', () => {
+ it('returns true, regardless of size', () => {
+ const checkArgs = checkSetup(
'x' +
@@ -83,8 +82,8 @@ describe('target-size tests', function () {
assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']);
});
- it('returns true when obscured by another focusable widget', function () {
- var checkArgs = checkSetup(
+ it('returns true when obscured by another focusable widget', () => {
+ const checkArgs = checkSetup(
'x' +
@@ -97,8 +96,8 @@ describe('target-size tests', function () {
assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']);
});
- it('ignores obscuring element has pointer-events:none', function () {
- var checkArgs = checkSetup(
+ it('ignores obscuring element has pointer-events:none', () => {
+ const checkArgs = checkSetup(
'x' +
@@ -115,9 +114,9 @@ describe('target-size tests', function () {
});
});
- describe('when partially obscured', function () {
- it('returns true for focusable non-widgets', function () {
- var checkArgs = checkSetup(
+ describe('when partially obscured', () => {
+ it('returns true for focusable non-widgets', () => {
+ const checkArgs = checkSetup(
'' +
@@ -137,8 +136,8 @@ describe('target-size tests', function () {
assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']);
});
- it('returns true for non-focusable widgets', function () {
- var checkArgs = checkSetup(
+ it('returns true for non-focusable widgets', () => {
+ const checkArgs = checkSetup(
'' +
@@ -158,9 +157,9 @@ describe('target-size tests', function () {
assert.deepEqual(elmIds(checkContext._relatedNodes), ['#obscurer']);
});
- describe('by a focusable widget', function () {
- it('returns true for obscured targets with sufficient space', function () {
- var checkArgs = checkSetup(
+ describe('by a focusable widget', () => {
+ it('returns true for obscured targets with sufficient space', () => {
+ const checkArgs = checkSetup(
'' +
@@ -202,8 +201,8 @@ describe('target-size tests', function () {
});
describe('for obscured targets with insufficient space', () => {
- it('returns false if all elements are tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns false if all elements are tabbable', () => {
+ const checkArgs = checkSetup(
'' +
@@ -227,8 +226,8 @@ describe('target-size tests', function () {
]);
});
- it('returns undefined if the target is not tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns undefined if the target is not tabbable', () => {
+ const checkArgs = checkSetup(
'' +
@@ -252,8 +251,8 @@ describe('target-size tests', function () {
]);
});
- it('returns undefined if the obscuring node is not tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns undefined if the obscuring node is not tabbable', () => {
+ const checkArgs = checkSetup(
'' +
@@ -279,8 +278,8 @@ describe('target-size tests', function () {
});
describe('that is a descendant', () => {
- it('returns false if the widget is tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns false if the widget is tabbable', () => {
+ const checkArgs = checkSetup(
`
`
@@ -289,8 +288,8 @@ describe('target-size tests', function () {
assert.isFalse(out);
});
- it('returns true if the widget is not tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns true if the widget is not tabbable', () => {
+ const checkArgs = checkSetup(
`
`
@@ -301,8 +300,8 @@ describe('target-size tests', function () {
});
describe('that is a descendant', () => {
- it('returns false if the widget is tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns false if the widget is tabbable', () => {
+ const checkArgs = checkSetup(
`
`
@@ -311,8 +310,8 @@ describe('target-size tests', function () {
assert.isFalse(out);
});
- it('returns true if the widget is not tabbable', function () {
- var checkArgs = checkSetup(
+ it('returns true if the widget is not tabbable', () => {
+ const checkArgs = checkSetup(
`
`
@@ -324,9 +323,9 @@ describe('target-size tests', function () {
});
});
- describe('with overflowing content', function () {
+ describe('with overflowing content', () => {
it('returns undefined target is too small', () => {
- var checkArgs = checkSetup(
+ const checkArgs = checkSetup(
''
);
assert.isUndefined(check.evaluate.apply(checkContext, checkArgs));
@@ -337,7 +336,7 @@ describe('target-size tests', function () {
});
it('returns true if target has sufficient size', () => {
- var checkArgs = checkSetup(
+ const checkArgs = checkSetup(
''
);
assert.isTrue(check.evaluate.apply(checkContext, checkArgs));
@@ -345,7 +344,7 @@ describe('target-size tests', function () {
describe('and partially obscured', () => {
it('is undefined when unobscured area is too small', () => {
- var checkArgs = checkSetup(
+ const checkArgs = checkSetup(
'' +
' ' +
'
' +
@@ -359,7 +358,7 @@ describe('target-size tests', function () {
});
it('is true when unobscured area is sufficient', () => {
- var checkArgs = checkSetup(
+ const checkArgs = checkSetup(
'' +
' ' +
'
' +
@@ -371,7 +370,7 @@ describe('target-size tests', function () {
describe('and fully obscured', () => {
it('is undefined', () => {
- var checkArgs = checkSetup(
+ const checkArgs = checkSetup(
'' +
' ' +
'
' +
@@ -386,8 +385,8 @@ describe('target-size tests', function () {
});
});
- it('works across shadow boundaries', function () {
- var checkArgs = shadowCheckSetup(
+ it('works across shadow boundaries', () => {
+ const checkArgs = shadowCheckSetup(
'' +
'';
+ const target = fixture.querySelector('#target');
+ const shadow = fixture
+ .querySelector('#shadow')
+ .attachShadow({ mode: 'open' });
+ shadow.innerHTML =
+ '