Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(color-contrast): correctly compute color-contrast of truncated children #3203

Merged
merged 12 commits into from
Oct 18, 2021
101 changes: 61 additions & 40 deletions lib/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,60 @@ function elmPartiallyObscured(elm, bgElm, bgColor) {
return obscured;
}

/**
* Get the page background color.
* @private
* @param {Element} elm
* @param {Boolean} stackContainsBody
* @return {Colors[]}
*/
function getPageBackgroundColors(elm, stackContainsBody) {
straker marked this conversation as resolved.
Show resolved Hide resolved
const pageColors = [];

// Body can sometimes appear out of order in the stack:
// 1) Body is not the first element due to negative z-index elements
// 2) Elements are positioned outside of body's rect coordinates
// (see https://github.com/dequelabs/axe-core/issues/1456)
// In those instances we want to reinsert body back into the element stack
// when not using the root document element as the html canvas for bgcolor
if (!stackContainsBody) {
// if the html element defines a bgColor and body defines a
// bgColor but its height is not the full viewport, then the html
// bgColor fills the full viewport and body bgColor only fills to
// its size. however, if the html element does not define a
// bgColor, then the body bgColor fills the full viewport. so if
// the body wasn't added to the elmStack, we need to know which
// bgColor to get (html or body)
const html = document.documentElement;
const body = document.body;
const htmlStyle = window.getComputedStyle(html);
const bodyStyle = window.getComputedStyle(body);
const htmlBgColor = getOwnBackgroundColor(htmlStyle);
const bodyBgColor = getOwnBackgroundColor(bodyStyle);
const bodyBgColorApplies =
bodyBgColor.alpha !== 0 && visuallyContains(elm, body);

if (
(bodyBgColor.alpha !== 0 && htmlBgColor.alpha === 0) ||
(bodyBgColorApplies && bodyBgColor.alpha !== 1)
) {
pageColors.unshift(bodyBgColor);
}

if (
htmlBgColor.alpha !== 0 &&
(!bodyBgColorApplies || (bodyBgColorApplies && bodyBgColor.alpha !== 1))
) {
pageColors.unshift(htmlBgColor);
}
}

// default page background is white
pageColors.unshift(new Color(255, 255, 255, 1));

return pageColors;
}

/**
* Returns background color for element
* Uses getBackgroundStack() to get all elements rendered underneath the current element,
Expand Down Expand Up @@ -79,49 +133,16 @@ function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
return null;
}

// Body can sometimes appear out of order in the stack:
// 1) Body is not the first element due to negative z-index elements
// 2) Elements are positioned outside of body's rect coordinates
// (see https://github.com/dequelabs/axe-core/issues/1456)
// In those instances we want to reinsert body back into the element stack
// when not using the root document element as the html canvas for bgcolor
if (elmStack.indexOf(document.body) === -1) {
// if the html element defines a bgColor and body defines a
// bgColor but its height is not the full viewport, then the html
// bgColor fills the full viewport and body bgColor only fills to
// its size. however, if the html element does not define a
// bgColor, then the body bgColor fills the full viewport. so if
// the body wasn't added to the elmStack, we need to know which
// bgColor to get (html or body)
const html = document.documentElement;
const body = document.body;
const htmlStyle = window.getComputedStyle(html);
const bodyStyle = window.getComputedStyle(body);
const htmlBgColor = getOwnBackgroundColor(htmlStyle);
const bodyBgColor = getOwnBackgroundColor(bodyStyle);
const bodyBgColorApplies =
bodyBgColor.alpha !== 0 && visuallyContains(elm, body);

if (
(bgColors.alpha !== 0 && htmlBgColor.alpha === 0) ||
(bodyBgColorApplies && bodyBgColor.alpha !== 1)
) {
bgColors.unshift(bodyBgColor);
}

if (
htmlBgColor.alpha !== 0 &&
(!bodyBgColorApplies || (bodyBgColorApplies && bodyBgColor.alpha !== 1))
) {
bgColors.unshift(htmlBgColor);
}
}
const pageBgs = getPageBackgroundColors(
elm,
elmStack.includes(document.body)
);
bgColors.unshift(...pageBgs);

// Mix the colors together, on top of a default white. Colors must be mixed
// in bottom up order (background to foreground order) to produce the correct
// Mix the colors together. Colors must be mixed in bottom up
// order (background to foreground order) to produce the correct
// result.
// @see https://github.com/dequelabs/axe-core/issues/2924
bgColors.unshift(new Color(255, 255, 255, 1));
var colors = bgColors.reduce((bgColor, fgColor) => {
return flattenColors(fgColor, bgColor);
});
Expand Down
2 changes: 1 addition & 1 deletion lib/commons/color/get-background-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function sortPageBackground(elmStack) {
if (htmlIndex > 0) {
bgNodes.splice(htmlIndex, 1);

// Put the body background as the lowest element
// Put the html background as the lowest element
bgNodes.push(document.documentElement);
}
}
Expand Down
47 changes: 13 additions & 34 deletions test/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ describe('color.getBackgroundColor', function() {
var fixture = document.getElementById('fixture');

var shadowSupported = axe.testUtils.shadowSupport.v1;
var origBodyBg;
var origHtmlBg;

before(function() {
origBodyBg = document.body.style.background;
origHtmlBg = document.documentElement.style.background;
});

afterEach(function() {
document.getElementById('fixture').innerHTML = '';
document.body.style.background = origBodyBg;
document.documentElement.style.background = origHtmlBg;

axe.commons.color.incompleteData.clear();
axe._tree = undefined;
});
Expand Down Expand Up @@ -635,7 +644,6 @@ describe('color.getBackgroundColor', function() {
'style="z-index:-1; position:absolute; width:100%; height:2em; background: #000"></div>' +
'<div id="target">Some text</div>';

var orig = document.body.style.background;
document.body.style.background = '#FFF';
axe.testUtils.flatTreeSetup(fixture);
var actual = axe.commons.color.getBackgroundColor(
Expand All @@ -649,8 +657,6 @@ describe('color.getBackgroundColor', function() {
assert.closeTo(actual.green, expected.green, 0.5);
assert.closeTo(actual.blue, expected.blue, 0.5);
assert.closeTo(actual.alpha, expected.alpha, 0.1);

document.body.style.background = orig;
});

it('should return null for negative z-index element when html and body have a background', function() {
Expand All @@ -659,9 +665,7 @@ describe('color.getBackgroundColor', function() {
'<div id="target" ' +
'style="z-index:-1; position:absolute; top: 100px; width:100%; height:2em; background: #000"></div>';

var origHtml = document.body.style.background;
document.body.style.background = '#0F0';
var origBody = document.body.style.background;
document.documentElement.style.background = '#0F0';
document.body.style.background = '#FFF';
axe.testUtils.flatTreeSetup(fixture);
var actual = axe.commons.color.getBackgroundColor(
Expand All @@ -670,9 +674,6 @@ describe('color.getBackgroundColor', function() {
);

assert.isNull(actual);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow... That's all I have to say about this.


document.documentElement.style.background = origHtml;
document.body.style.background = origBody;
});

it('should return background color for inline elements that do not fit the viewport', function() {
Expand Down Expand Up @@ -712,7 +713,7 @@ describe('color.getBackgroundColor', function() {

// size body element so that target element is positioned outside of background
var originalHeight = document.body.style.height;
var originalBg = document.body.style.background;
var originalMargin = document.body.style.margin;
document.body.style.height = '1px';
document.body.style.background = '#000';
document.body.style.margin = 0;
Expand All @@ -727,7 +728,7 @@ describe('color.getBackgroundColor', function() {
assert.closeTo(actual.alpha, 1, 0);

document.body.style.height = originalHeight;
document.body.style.background = originalBg;
document.body.style.margin = originalMargin;
});

it('should return the html canvas bgColor when element content does not overlap with body', function() {
Expand All @@ -736,8 +737,6 @@ describe('color.getBackgroundColor', function() {

// size body element so that target element is positioned outside of background
var originalHeight = document.body.style.height;
var originalBg = document.body.style.background;
var originalRootBg = document.documentElement.style.background;
document.body.style.height = '1px';
document.body.style.background = '#0f0';
document.documentElement.style.background = '#f00';
Expand All @@ -752,8 +751,6 @@ describe('color.getBackgroundColor', function() {
assert.closeTo(actual.alpha, 1, 0);

document.body.style.height = originalHeight;
document.body.style.background = originalBg;
document.documentElement.style.background = originalRootBg;
});

(shadowSupported ? it : xit)('finds colors in shadow boundaries', function() {
Expand Down Expand Up @@ -998,7 +995,6 @@ describe('color.getBackgroundColor', function() {
describe('body and document', function() {
it('returns the body background', function() {
fixture.innerHTML = '<div id="target">elm</div>';
var orig = document.body.style.background;
document.body.style.background = '#F00';

axe.testUtils.flatTreeSetup(fixture);
Expand All @@ -1007,7 +1003,6 @@ describe('color.getBackgroundColor', function() {
[]
);
var expected = new axe.commons.color.Color(255, 0, 0, 1);
document.body.style.background = orig;

assert.closeTo(actual.red, expected.red, 0.5);
assert.closeTo(actual.green, expected.green, 0.5);
Expand All @@ -1017,7 +1012,6 @@ describe('color.getBackgroundColor', function() {

it('returns the body background even when the body is MUCH larger than the screen', function() {
fixture.innerHTML = '<div id="target" style="height:20000px;">elm</div>';
var orig = document.body.style.background;
document.body.style.background = '#F00';

axe.testUtils.flatTreeSetup(fixture);
Expand All @@ -1026,7 +1020,6 @@ describe('color.getBackgroundColor', function() {
[]
);
var expected = new axe.commons.color.Color(255, 0, 0, 1);
document.body.style.background = orig;

assert.closeTo(actual.red, expected.red, 0.5);
assert.closeTo(actual.green, expected.green, 0.5);
Expand All @@ -1036,7 +1029,6 @@ describe('color.getBackgroundColor', function() {

it('returns the html background', function() {
fixture.innerHTML = '<div id="target"><label>elm<input></label></div>';
var orig = document.documentElement.style.background;
document.documentElement.style.background = '#0F0';

axe.testUtils.flatTreeSetup(fixture);
Expand All @@ -1045,7 +1037,6 @@ describe('color.getBackgroundColor', function() {
[]
);
var expected = new axe.commons.color.Color(0, 255, 0, 1);
document.documentElement.style.background = orig;

assert.closeTo(actual.red, expected.red, 0.5);
assert.closeTo(actual.green, expected.green, 0.5);
Expand All @@ -1056,9 +1047,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>';
var origHtml = document.documentElement.style.background;
document.documentElement.style.background = '#0F0';
var origBody = document.body.style.background;
document.body.style.background = '#00F';

axe.testUtils.flatTreeSetup(fixture);
Expand All @@ -1067,8 +1056,6 @@ describe('color.getBackgroundColor', function() {
[]
);
var expected = new axe.commons.color.Color(0, 255, 0, 1);
document.documentElement.style.background = origHtml;
document.body.style.background = origBody;

assert.closeTo(actual.red, expected.red, 0.5);
assert.closeTo(actual.green, expected.green, 0.5);
Expand All @@ -1078,9 +1065,7 @@ describe('color.getBackgroundColor', function() {

it('returns the body background when body does cover the element', function() {
fixture.innerHTML = '<div id="target"><label>elm<input></label></div>';
var origHtml = document.documentElement.style.background;
document.documentElement.style.background = '#0F0';
var origBody = document.body.style.background;
document.body.style.background = '#00F';

axe.testUtils.flatTreeSetup(fixture);
Expand All @@ -1089,8 +1074,6 @@ describe('color.getBackgroundColor', function() {
[]
);
var expected = new axe.commons.color.Color(0, 0, 255, 1);
document.documentElement.style.background = origHtml;
document.body.style.background = origBody;

assert.closeTo(actual.red, expected.red, 0.5);
assert.closeTo(actual.green, expected.green, 0.5);
Expand All @@ -1100,9 +1083,7 @@ describe('color.getBackgroundColor', function() {

it('returns both the html and body background if the body has alpha', function() {
fixture.innerHTML = '<div id="target"><label>elm<input></label></div>';
var origHtml = document.documentElement.style.background;
document.documentElement.style.background = '#0F0';
var origBody = document.body.style.background;
document.body.style.background = 'rgba(0, 0, 255, 0.5)';

axe.testUtils.flatTreeSetup(fixture);
Expand All @@ -1111,8 +1092,6 @@ describe('color.getBackgroundColor', function() {
[]
);
var expected = new axe.commons.color.Color(0, 128, 128, 1);
document.documentElement.style.background = origHtml;
document.body.style.background = origBody;

assert.closeTo(actual.red, expected.red, 0.5);
assert.closeTo(actual.green, expected.green, 0.5);
Expand Down
2 changes: 1 addition & 1 deletion test/commons/dom/visually-contains.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('dom.visuallyContains', function() {
it('should return true for child with truncated text', function() {
var target = queryFixture(
'<p style="max-width: 200px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">' +
'<span id="target">This text will fail! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fuscemi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla,sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero.</span>' +
'<span id="target">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fuscemi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla,sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero.</span>' +
'</p>'
);
var result = axe.commons.dom.visuallyContains(
Expand Down
24 changes: 12 additions & 12 deletions test/integration/rules/color-contrast/color-contrast.html
Original file line number Diff line number Diff line change
Expand Up @@ -259,17 +259,17 @@
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 200px; background-color: #FFF"
>
<span id="pass9" style="color: #000">
This text will fail! Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget,
vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est
justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a
fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in
libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit
volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut
tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan
quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna
vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada
tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta,
ipsum turpis bibendum ligula, at tempor felis ante non libero.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin
quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin
vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu
facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim
nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque
imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna,
mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh,
quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla
ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim.
Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris
dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor
felis ante non libero.
</span>
</p>