Skip to content

Commit

Permalink
fix(color-contrast): correctly apply page background color (#3207)
Browse files Browse the repository at this point in the history
* fixes

* finalize

* sort

* test

* fix tests

* Update lib/commons/color/get-background-stack.js

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>

* fixes

* fix(color-contrast): correctly apply page background color

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>
  • Loading branch information
straker and WilcoFiers authored Oct 13, 2021
1 parent 8f84d22 commit fbc581d
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 101 deletions.
111 changes: 86 additions & 25 deletions lib/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,23 @@ import flattenShadowColors from './flatten-shadow-colors';
import getTextShadowColors from './get-text-shadow-colors';
import visuallyContains from '../dom/visually-contains';

/**
* Determine if element is partially overlapped, triggering a Can't Tell result
* @private
* @param {Element} elm
* @param {Element} bgElm
* @param {Object} bgColor
* @return {Boolean}
*/
function elmPartiallyObscured(elm, bgElm, bgColor) {
var obscured =
elm !== bgElm && !visuallyContains(elm, bgElm) && bgColor.alpha !== 0;
if (obscured) {
incompleteData.set('bgColor', 'elmPartiallyObscured');
}
return obscured;
}

/**
* Returns background color for element
* Uses getBackgroundStack() to get all elements rendered underneath the current element,
* to help determine the composite background color.
*
* @method getBackgroundColor
* @memberof axe.commons.color
* @param {Element} elm Element to determine background color
* @param {Array} [bgElms=[]] elements to inspect
* @param {Number} shadowOutlineEmMax Thickness of `text-shadow` at which it becomes a background color
* @param {Element} elm Element to determine background color
* @param {Array} [bgElms=[]] elements to inspect
* @param {Number} shadowOutlineEmMax Thickness of `text-shadow` at which it becomes a background color
* @returns {Color}
*/
function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
export default function getBackgroundColor(
elm,
bgElms = [],
shadowOutlineEmMax = 0.1
) {
let bgColors = getTextShadowColors(elm, { minRatio: shadowOutlineEmMax });
if (bgColors.length) {
bgColors = [bgColors.reduce(flattenShadowColors)];
Expand Down Expand Up @@ -79,15 +66,89 @@ function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
return null;
}

// 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
const pageBgs = getPageBackgroundColors(
elm,
elmStack.includes(document.body)
);
bgColors.unshift(...pageBgs);

// 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);
});
return colors;
}

export default getBackgroundColor;
/**
* Determine if element is partially overlapped, triggering a Can't Tell result
* @private
* @param {Element} elm
* @param {Element} bgElm
* @param {Object} bgColor
* @return {Boolean}
*/
function elmPartiallyObscured(elm, bgElm, bgColor) {
var obscured =
elm !== bgElm && !visuallyContains(elm, bgElm) && bgColor.alpha !== 0;
if (obscured) {
incompleteData.set('bgColor', 'elmPartiallyObscured');
}
return obscured;
}

/**
* Get the page background color.
* @private
* @param {Element} elm
* @param {Boolean} stackContainsBody
* @return {Colors[]}
*/
function getPageBackgroundColors(elm, stackContainsBody) {
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 need to determine if we should use the
// body background or the html background color
if (!stackContainsBody) {
// if the html element defines a bgColor and body defines a
// bgColor but body's 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;
}
42 changes: 23 additions & 19 deletions lib/commons/color/get-background-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,32 +69,36 @@ function sortPageBackground(elmStack) {
const bodyIndex = elmStack.indexOf(document.body);
const bgNodes = elmStack;

// 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
// prettier-ignore
const sortBodyElement =
bodyIndex > 1 || // negative z-index elements
bodyIndex === -1; // element does not intersect with body

// body can sometimes appear out of order in the stack when it
// is not the first element due to negative z-index elements.
// however, we only want to change order if the html element
// does not define a background color (ya, it's a strange edge
// case. it turns out that if html defines a background it treats
// body as a normal element, but if it doesn't then body is treated
// as the "html" element)
const htmlBgColor = getOwnBackgroundColor(
window.getComputedStyle(document.documentElement)
);
if (
sortBodyElement &&
!elementHasImage(document.documentElement) &&
getOwnBackgroundColor(window.getComputedStyle(document.documentElement))
.alpha === 0
bodyIndex > 1 &&
htmlBgColor.alpha === 0 &&
!elementHasImage(document.documentElement)
) {
// Only remove document.body if it was originally contained within the element stack
if (bodyIndex > 1) {
bgNodes.splice(bodyIndex, 1);

// Put the body background as the lowest element
bgNodes.push(document.body);
}
// Remove document element since body will be used for bgcolor
bgNodes.splice(elmStack.indexOf(document.documentElement), 1);

// Put the body background as the lowest element
bgNodes.push(document.body);
const htmlIndex = bgNodes.indexOf(document.documentElement);
if (htmlIndex > 0) {
bgNodes.splice(htmlIndex, 1);

// Put the html background as the lowest element
bgNodes.push(document.documentElement);
}
}
return bgNodes;
}
Expand Down
Loading

0 comments on commit fbc581d

Please sign in to comment.