diff --git a/lib/commons/dom/get-visible-child-text-rects.js b/lib/commons/dom/get-visible-child-text-rects.js index 8ac6166012..02611798e8 100644 --- a/lib/commons/dom/get-visible-child-text-rects.js +++ b/lib/commons/dom/get-visible-child-text-rects.js @@ -39,8 +39,14 @@ const getVisibleChildTextRects = memoize( * @see https://github.com/dequelabs/axe-core/issues/2178 * @see https://github.com/dequelabs/axe-core/issues/2483 * @see https://github.com/dequelabs/axe-core/issues/2681 + * + * also need to resize the nodeRect to fit within the bounds of any overflow: hidden ancestors. + * + * @see https://github.com/dequelabs/axe-core/issues/4253 */ - return clientRects.length ? clientRects : [nodeRect]; + return clientRects.length + ? clientRects + : filterHiddenRects([nodeRect], overflowHiddenNodes); } ); export default getVisibleChildTextRects; diff --git a/lib/rules/color-contrast-matches.js b/lib/rules/color-contrast-matches.js index d95b1c3890..b76921bc37 100644 --- a/lib/rules/color-contrast-matches.js +++ b/lib/rules/color-contrast-matches.js @@ -4,7 +4,8 @@ import { findUpVirtual, visuallyOverlaps, getRootNode, - isInert + isInert, + getOverflowHiddenAncestors } from '../commons/dom'; import { visibleVirtual, @@ -12,6 +13,7 @@ import { sanitize, isIconLigature } from '../commons/text'; +import { rectsOverlap } from '../commons/math'; import { isDisabled } from '../commons/forms'; import { getNodeFromTree, querySelectorAll, tokenList } from '../core/utils'; @@ -147,14 +149,22 @@ function colorContrastMatches(node, virtualNode) { } } - const rects = range.getClientRects(); - for (let index = 0; index < rects.length; index++) { + const rects = Array.from(range.getClientRects()); + const clippingAncestors = getOverflowHiddenAncestors(virtualNode); + return rects.some(rect => { //check to see if the rectangle impinges - if (visuallyOverlaps(rects[index], node)) { - return true; + const overlaps = visuallyOverlaps(rect, node); + + if (!clippingAncestors.length) { + return overlaps; } - } - return false; + + const withinOverflow = clippingAncestors.some(overflowNode => { + return rectsOverlap(rect, overflowNode.boundingClientRect); + }); + + return overlaps && withinOverflow; + }); } export default colorContrastMatches; diff --git a/test/commons/dom/get-visible-child-text-rects.js b/test/commons/dom/get-visible-child-text-rects.js index 126e79d037..844e8ea3fb 100644 --- a/test/commons/dom/get-visible-child-text-rects.js +++ b/test/commons/dom/get-visible-child-text-rects.js @@ -120,4 +120,20 @@ describe('dom.getVisibleChildTextRects', () => { assert.lengthOf(actual, 2); }); + + it('changes nodeRect size if all text rects got outside ancestor overflow', () => { + fixtureSetup(` +
+
+
Hello World
+
+
+ `); + const node = fixture.querySelector('#target'); + const actual = getVisibleChildTextRects(node); + const rect = getClientRects(node)[0]; + const expected = new DOMRect(rect.left, rect.top, 25, rect.height); + + assertRectsEqual(actual, [expected]); + }); }); diff --git a/test/integration/rules/color-contrast/color-contrast.html b/test/integration/rules/color-contrast/color-contrast.html index 5a6cc1e690..f866b05ca1 100644 --- a/test/integration/rules/color-contrast/color-contrast.html +++ b/test/integration/rules/color-contrast/color-contrast.html @@ -459,3 +459,9 @@ > Hello world + +
+
+
Hello World
+
+
diff --git a/test/rule-matches/color-contrast-matches.js b/test/rule-matches/color-contrast-matches.js index 382457119c..fc41e301ee 100644 --- a/test/rule-matches/color-contrast-matches.js +++ b/test/rule-matches/color-contrast-matches.js @@ -418,6 +418,19 @@ describe('color-contrast-matches', function () { assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); }); + it('should not match text outside overflow', () => { + fixture.innerHTML = ` +
+
+
Hello World
+
+
+ `; + var target = fixture.querySelector('#target'); + axe.testUtils.flatTreeSetup(fixture); + assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target))); + }); + if (shadowSupport) { it('should match a descendant of an element across a shadow boundary', function () { fixture.innerHTML =