Skip to content

Commit

Permalink
fix(color-contrast): inconsistency of bgOverlap message based on scro…
Browse files Browse the repository at this point in the history
…ll (#3310)

* fix(color-contrast): inconsistency of bgOverlap message based on scroll

* it should return null for inline elements with position:absolute

* Guessing at how to fix IE
  • Loading branch information
WilcoFiers authored Nov 29, 2021
1 parent bec20fc commit 25eff98
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 36 deletions.
59 changes: 25 additions & 34 deletions lib/commons/color/get-background-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,50 @@ 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');
if (!display.includes('inline')) {
return false;
}
// IE needs this; It doesn't set display:block when position is set
const position = style.getPropertyValue('position')
return position === 'static';
}

/**
* 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
* @param {Element} originalElm
* @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:
// <p>text <img></p>; We don't care about the <img> element,
// since it does not overlap the text inside of <p>
elmStack.splice(i, 1);
}

return false;
}

Expand Down
2 changes: 1 addition & 1 deletion test/checks/color/color-contrast.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ describe('color-contrast', function() {
});

it('should return true when a label wraps a text input', function() {
fixtureSetup('<label id="target">' + 'My text <input type="text"></label>');
fixtureSetup('<label id="target">My text <input type="text"></label>');
var target = fixture.querySelector('#target');
var virtualNode = axe.utils.getNodeFromTree(target);
var result = contrastEvaluate.call(checkContext, target, {}, virtualNode);
Expand Down
51 changes: 50 additions & 1 deletion test/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
'<div style="height: 1em; overflow: auto; position: relative;">' +
' <div style="background: rgba(0, 255, 255, 0.7); ' +
' margin-bottom: -1em; height: 1em; position:relative;"></div>' +
' <div id="target">foo</div>' +
'</div>';
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 =
'<div style="width:100%; height: 100px; background: rgba(0, 0, 0, 0.5)"></div>' +
Expand Down Expand Up @@ -465,6 +480,40 @@ describe('color.getBackgroundColor', function() {
assert.equal(actual.alpha, expected.alpha);
});

it('handles nested inline elements in the middle of a text', function () {
fixture.innerHTML =
'<div style="height: 1em; overflow:auto; background: cyan">' +
' <br>' +
' <b id="target">Text <i style="display: inline-block">'+
' <s><img width="100" height="16"></s>' +
' </i></b>' +
'</div>';

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 return null for inline elements with position:absolute', function () {
fixture.innerHTML =
'<div style="height: 1em; overflow:auto; position: relative">' +
' <br>' +
' <b id="target">' +
' <img style="width:100px; height:16px; position:absolute"> Text' +
' </b>' +
'</div>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var actual = axe.commons.color.getBackgroundColor(target);
assert.equal(axe.commons.color.incompleteData.get('bgColor'), 'bgOverlap');
assert.isNull(actual);
});

it('should ignore inline ancestors of non-overlapping elements', function() {
fixture.innerHTML =
'<div style="position:relative;">' +
Expand Down Expand Up @@ -1046,7 +1095,7 @@ describe('color.getBackgroundColor', function() {

it('returns the html background when body does not cover the element', function() {
fixture.innerHTML =
'<div id="target" style="position: absolute; top: 1000px;"><label>elm<input></label></div>';
'<div id="target" style="position: absolute; top: 1000px;">elm<input></div>';
document.documentElement.style.background = '#0F0';
document.body.style.background = '#00F';

Expand Down

0 comments on commit 25eff98

Please sign in to comment.