From 3f0fd316b1650ccdfdb1cf672b43f96ce1ab9a5f Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 24 Nov 2021 13:01:20 +0100 Subject: [PATCH] fix(color-contrast): inconsistency of bgOverlap message based on scroll --- lib/commons/color/get-background-stack.js | 54 ++++++++-------------- test/checks/color/color-contrast.js | 2 +- test/commons/color/get-background-color.js | 36 ++++++++++++++- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/lib/commons/color/get-background-stack.js b/lib/commons/color/get-background-stack.js index d06d071564..0b524f070b 100644 --- a/lib/commons/color/get-background-stack.js +++ b/lib/commons/color/get-background-stack.js @@ -2,39 +2,28 @@ import filteredRectStack from './filtered-rect-stack'; import elementHasImage from './element-has-image'; import getOwnBackgroundColor from './get-own-background-color'; import incompleteData from './incomplete-data'; -import shadowElementsFromPoint from '../dom/shadow-elements-from-point'; import reduceToElementsBelowFloating from '../dom/reduce-to-elements-below-floating'; /** - * Determines overlap of node's content with a bgNode. Used for inline elements + * Determine if element B is an inline descendant of A * @private - * @param {Element} targetElement - * @param {Element} bgNode + * @param {Element} node + * @param {Element} descendant * @return {Boolean} */ -function contentOverlapping(targetElement, bgNode) { - // get content box of target element - // check to see if the current bgNode is overlapping - var targetRect = targetElement.getClientRects()[0]; - var obscuringElements = shadowElementsFromPoint( - targetRect.left, - targetRect.top - ); - if (obscuringElements) { - for (var i = 0; i < obscuringElements.length; i++) { - if ( - obscuringElements[i] !== targetElement && - obscuringElements[i] === bgNode - ) { - return true; - } - } +function isInlineDescendant(node, descendant) { + const CONTAINED_BY = Node.DOCUMENT_POSITION_CONTAINED_BY; + // eslint-disable-next-line no-bitwise + if (!(node.compareDocumentPosition(descendant) & CONTAINED_BY)) { + return false; } - return false; + const style = window.getComputedStyle(descendant) + const display = style.getPropertyValue('display'); + return display.includes('inline') } /** - * Calculate alpha transparency of a background element obscuring the current node + * Determine if the element obscures / overlaps with the text * @private * @param {Number} elmIndex * @param {Array} elmStack @@ -42,19 +31,16 @@ function contentOverlapping(targetElement, bgNode) { * @return {Number|undefined} */ function calculateObscuringElement(elmIndex, elmStack, originalElm) { - if (elmIndex > 0) { - // there are elements above our element, check if they contribute to the background - for (var i = elmIndex - 1; i >= 0; i--) { - const bgElm = elmStack[i]; - if (contentOverlapping(originalElm, bgElm)) { - return true; - } else { - // remove elements not contributing to the background - elmStack.splice(i, 1); - } + // Reverse order, so that we can safely splice + for (let i = elmIndex - 1; i >= 0; i--) { + if (!isInlineDescendant(originalElm, elmStack[i])) { + return true; } + // Ignore inline descendants, for example: + //

text

; We don't care about the element, + // since it does not overlap the text inside of

+ elmStack.splice(i, 1); } - return false; } diff --git a/test/checks/color/color-contrast.js b/test/checks/color/color-contrast.js index 339e5d1a3b..67613e288f 100644 --- a/test/checks/color/color-contrast.js +++ b/test/checks/color/color-contrast.js @@ -275,7 +275,7 @@ describe('color-contrast', function() { }); it('should return true when a label wraps a text input', function() { - fixtureSetup(''); + fixtureSetup(''); var target = fixture.querySelector('#target'); var virtualNode = axe.utils.getNodeFromTree(target); var result = contrastEvaluate.call(checkContext, target, {}, virtualNode); diff --git a/test/commons/color/get-background-color.js b/test/commons/color/get-background-color.js index 6770dac8b5..2916012309 100644 --- a/test/commons/color/get-background-color.js +++ b/test/commons/color/get-background-color.js @@ -200,6 +200,21 @@ describe('color.getBackgroundColor', function() { assert.isNull(actual); }); + it('should return null if something non-opaque is obscuring it, scrolled out of view', function() { + fixture.innerHTML = + '

' + + '
' + + '
foo
' + + '
'; + axe.testUtils.flatTreeSetup(fixture); + var actual = axe.commons.color.getBackgroundColor( + document.getElementById('target') + ); + assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgOverlap'); + assert.isNull(actual); + }); + it('should return an actual if something opaque is obscuring it', function() { fixture.innerHTML = '
' + @@ -465,6 +480,25 @@ describe('color.getBackgroundColor', function() { assert.equal(actual.alpha, expected.alpha); }); + it('handles nested inline elements in the middle of a text', function () { + fixture.innerHTML = + '
' + + '
' + + ' Text '+ + ' ' + + ' ' + + '
'; + + var target = fixture.querySelector('#target'); + var bgNodes = []; + axe.testUtils.flatTreeSetup(fixture); + var actual = axe.commons.color.getBackgroundColor(target, bgNodes); + assert.equal(actual.red, 0); + assert.equal(actual.green, 255); + assert.equal(actual.blue, 255); + assert.equal(actual.alpha, 1); + }); + it('should ignore inline ancestors of non-overlapping elements', function() { fixture.innerHTML = '
' + @@ -1046,7 +1080,7 @@ describe('color.getBackgroundColor', function() { it('returns the html background when body does not cover the element', function() { fixture.innerHTML = - '
'; + '
elm
'; document.documentElement.style.background = '#0F0'; document.body.style.background = '#00F';