From 00ec8cad8b2c061c26fda93136c7ffb92df0e346 Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Tue, 12 Oct 2021 14:32:22 -0600
Subject: [PATCH 01/10] fixes
---
lib/commons/color/get-background-color.js | 37 ++++
lib/commons/color/get-background-stack.js | 42 ++--
test/commons/color/get-background-color.js | 185 +++++++++++++-----
.../rules/color-contrast/color-contrast.html | 25 +++
.../rules/color-contrast/color-contrast.json | 3 +-
5 files changed, 225 insertions(+), 67 deletions(-)
diff --git a/lib/commons/color/get-background-color.js b/lib/commons/color/get-background-color.js
index 230fa9d568..991edf7ed5 100644
--- a/lib/commons/color/get-background-color.js
+++ b/lib/commons/color/get-background-color.js
@@ -79,6 +79,43 @@ 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 && visuallyContains(elm, body);
+
+ if (
+ (!htmlBgColor && bodyBgColorApplies) ||
+ (bodyBgColorApplies && bodyBgColorApplies.alpha !== 1)
+ ) {
+ bgColors.unshift(bodyBgColor);
+ }
+
+ if (
+ htmlBgColor &&
+ (!bodyBgColorApplies || (bodyBgColorApplies && bodyBgColor.alpha !== 1))
+ ) {
+ bgColors.unshift(htmlBgColor);
+ }
+ }
+
// 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
// result.
diff --git a/lib/commons/color/get-background-stack.js b/lib/commons/color/get-background-stack.js
index cfe0721976..6173a43f7e 100644
--- a/lib/commons/color/get-background-stack.js
+++ b/lib/commons/color/get-background-stack.js
@@ -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 body background as the lowest element
+ bgNodes.push(document.documentElement);
+ }
}
return bgNodes;
}
diff --git a/test/commons/color/get-background-color.js b/test/commons/color/get-background-color.js
index f22ac33fd1..b56daa2def 100644
--- a/test/commons/color/get-background-color.js
+++ b/test/commons/color/get-background-color.js
@@ -653,61 +653,26 @@ describe('color.getBackgroundColor', function() {
document.body.style.background = orig;
});
- it('returns the body background', function() {
- fixture.innerHTML = '
elm
';
- var orig = document.body.style.background;
- document.body.style.background = '#F00';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- 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);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns the body background even when the body is MUCH larger than the screen', function() {
- fixture.innerHTML = '
elm
';
- var orig = document.body.style.background;
- document.body.style.background = '#F00';
+ it('should return null for negative z-index element when html and body have a background', function() {
+ fixture.innerHTML =
+ '' +
+ '';
+ var origHtml = document.body.style.background;
+ document.body.style.background = '#0F0';
+ var origBody = document.body.style.background;
+ document.body.style.background = '#FFF';
axe.testUtils.flatTreeSetup(fixture);
var actual = axe.commons.color.getBackgroundColor(
document.getElementById('target'),
[]
);
- 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);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns the html background', function() {
- fixture.innerHTML = '';
- var orig = document.documentElement.style.background;
- document.documentElement.style.background = '#0F0';
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(0, 255, 0, 1);
- document.documentElement.style.background = orig;
+ assert.isNull(actual);
- assert.closeTo(actual.red, expected.red, 0.5);
- 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.documentElement.style.background = origHtml;
+ document.body.style.background = origBody;
});
it('should return background color for inline elements that do not fit the viewport', function() {
@@ -1029,4 +994,130 @@ describe('color.getBackgroundColor', function() {
assert.closeTo(actual.blue, expected.blue, 0.5);
assert.closeTo(actual.alpha, expected.alpha, 0.1);
});
+
+ describe('body and document', function() {
+ it('returns the body background', function() {
+ fixture.innerHTML = '
elm
';
+ var orig = document.body.style.background;
+ document.body.style.background = '#F00';
+
+ axe.testUtils.flatTreeSetup(fixture);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+
+ it('returns the body background even when the body is MUCH larger than the screen', function() {
+ fixture.innerHTML = '
elm
';
+ var orig = document.body.style.background;
+ document.body.style.background = '#F00';
+
+ axe.testUtils.flatTreeSetup(fixture);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+
+ it('returns the html background', function() {
+ fixture.innerHTML = '';
+ var orig = document.documentElement.style.background;
+ document.documentElement.style.background = '#0F0';
+
+ axe.testUtils.flatTreeSetup(fixture);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+
+ it('returns the html background when body does not cover the element', function() {
+ fixture.innerHTML =
+ '';
+ 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);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+
+ it('returns the body background when body does cover the element', function() {
+ fixture.innerHTML = '';
+ 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);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+
+ it('returns both the html and body background if the body has alpha', function() {
+ fixture.innerHTML = '';
+ 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);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+ });
});
diff --git a/test/integration/rules/color-contrast/color-contrast.html b/test/integration/rules/color-contrast/color-contrast.html
index 2a0d3ed153..f6d27c6e22 100644
--- a/test/integration/rules/color-contrast/color-contrast.html
+++ b/test/integration/rules/color-contrast/color-contrast.html
@@ -254,3 +254,28 @@
Hello World
+
+
+
+ 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.
+
+
+
+
+
Some text
+
diff --git a/test/integration/rules/color-contrast/color-contrast.json b/test/integration/rules/color-contrast/color-contrast.json
index a9724b8ea9..c661e1881c 100644
--- a/test/integration/rules/color-contrast/color-contrast.json
+++ b/test/integration/rules/color-contrast/color-contrast.json
@@ -11,7 +11,8 @@
["#pass7"],
["#pass7 > input"],
["#pass8"],
- ["#text-shadow-fg-pass"]
+ ["#text-shadow-fg-pass"],
+ ["#pass9"]
],
"incomplete": [
["#canttell1"],
From 0f1ed5f4dfde43afa10ac2a1572f2e8a17a2761d Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Tue, 12 Oct 2021 14:35:26 -0600
Subject: [PATCH 02/10] finalize
---
lib/commons/dom/visually-contains.js | 69 ++++++++++++---------------
test/commons/dom/visually-contains.js | 13 +++++
2 files changed, 43 insertions(+), 39 deletions(-)
diff --git a/lib/commons/dom/visually-contains.js b/lib/commons/dom/visually-contains.js
index c980ec1cb6..3d0b5adf1b 100644
--- a/lib/commons/dom/visually-contains.js
+++ b/lib/commons/dom/visually-contains.js
@@ -19,61 +19,52 @@ function getScrollAncestor(node) {
}
/**
- * Checks whether a parent element visually contains its child, either directly or via scrolling.
+ * Checks whether a parent element fully contains its child, either directly or via scrolling.
* Assumes that |parent| is an ancestor of |node|.
* @param {Element} node
* @param {Element} parent
* @return {boolean} True if node is visually contained within parent
*/
function contains(node, parent) {
- const rectBound = node.getBoundingClientRect();
- const margin = 0.01;
- const rect = {
- top: rectBound.top + margin,
- bottom: rectBound.bottom - margin,
- left: rectBound.left + margin,
- right: rectBound.right - margin
- };
-
- const parentRect = parent.getBoundingClientRect();
- const parentTop = parentRect.top;
- const parentLeft = parentRect.left;
- const parentScrollArea = {
- top: parentTop - parent.scrollTop,
- bottom: parentTop - parent.scrollTop + parent.scrollHeight,
- left: parentLeft - parent.scrollLeft,
- right: parentLeft - parent.scrollLeft + parent.scrollWidth
- };
-
const style = window.getComputedStyle(parent);
+ const overflow = style.getPropertyValue('overflow');
// if parent element is inline, scrollArea will be too unpredictable
if (style.getPropertyValue('display') === 'inline') {
return true;
}
- //In theory, we should just be able to look at the scroll area as a superset of the parentRect,
- //but that's not true in Firefox
- if (
- (rect.left < parentScrollArea.left && rect.left < parentRect.left) ||
- (rect.top < parentScrollArea.top && rect.top < parentRect.top) ||
- (rect.right > parentScrollArea.right && rect.right > parentRect.right) ||
- (rect.bottom > parentScrollArea.bottom && rect.bottom > parentRect.bottom)
- ) {
- return false;
- }
+ // use clientRects instead of boundingClientRect to account
+ // for truncation of text (one of the rects will be the size
+ // of the truncation)
+ // @see https://github.com/dequelabs/axe-core/issues/2669
+ const clientRects = Array.from(node.getClientRects());
+ // getBoundingClientRect prevents overrides of left/top
+ // (also can't destructure)
+ const boundingRect = parent.getBoundingClientRect();
+ const rect = {
+ left: boundingRect.left,
+ top: boundingRect.top,
+ width: boundingRect.width,
+ height: boundingRect.height
+ };
- if (rect.right > parentRect.right || rect.bottom > parentRect.bottom) {
- return (
- style.overflow === 'scroll' ||
- style.overflow === 'auto' ||
- style.overflow === 'hidden' ||
- parent instanceof window.HTMLBodyElement ||
- parent instanceof window.HTMLHtmlElement
- );
+ if (['scroll', 'auto'].includes(overflow)) {
+ rect.width = parent.scrollWidth;
+ rect.height = parent.scrollHeight;
}
- return true;
+ // check if any client rect is fully inside the parent rect
+ // @see https://gist.github.com/Daniel-Hug/d7984d82b58d6d2679a087d896ca3d2b
+ return clientRects.some(
+ clientRect =>
+ !(
+ clientRect.left < rect.left ||
+ clientRect.top < rect.top ||
+ clientRect.left + clientRect.width > rect.left + rect.width ||
+ clientRect.top + clientRect.height > rect.top + rect.height
+ )
+ );
}
/**
diff --git a/test/commons/dom/visually-contains.js b/test/commons/dom/visually-contains.js
index 482dbf5764..b47fdcc279 100644
--- a/test/commons/dom/visually-contains.js
+++ b/test/commons/dom/visually-contains.js
@@ -129,6 +129,19 @@ describe('dom.visuallyContains', function() {
assert.isTrue(result);
});
+ it('should return true for child with truncated text', function() {
+ var target = queryFixture(
+ '
' +
+ '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.' +
+ '
'
+ );
+ var result = axe.commons.dom.visuallyContains(
+ target.actualNode,
+ target.parent.actualNode
+ );
+ assert.isTrue(result);
+ });
+
(shadowSupported ? it : xit)(
'should return true when element is visually contained across shadow boundary',
function() {
From 1f18d9242b361679361ad341cf59095c42671729 Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Tue, 12 Oct 2021 14:35:48 -0600
Subject: [PATCH 03/10] sort
From c9a9d05eccc243ae5600dcbd52caa7aa17671fc8 Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Tue, 12 Oct 2021 14:47:35 -0600
Subject: [PATCH 04/10] test
---
test/commons/dom/visually-contains.js | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/test/commons/dom/visually-contains.js b/test/commons/dom/visually-contains.js
index b47fdcc279..598c3556dd 100644
--- a/test/commons/dom/visually-contains.js
+++ b/test/commons/dom/visually-contains.js
@@ -142,6 +142,18 @@ describe('dom.visuallyContains', function() {
assert.isTrue(result);
});
+ it('should return false if element is outside overflow hidden', function() {
+ var target = queryFixture(
+ '
' +
+ '
Some text
' +
+ '
'
+ );
+
+ var parent = fixture.querySelector('#parent');
+ var result = axe.commons.dom.visuallyContains(target.actualNode, parent);
+ assert.isFalse(result);
+ });
+
(shadowSupported ? it : xit)(
'should return true when element is visually contained across shadow boundary',
function() {
From 3a6e7a8717a6887c072a06eb8a50af4b29df1bf8 Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Tue, 12 Oct 2021 15:38:53 -0600
Subject: [PATCH 05/10] fix tests
---
lib/commons/color/get-background-color.js | 9 ++++----
lib/commons/dom/visually-contains.js | 21 ++++++++++++++++++-
.../rules/color-contrast/color-contrast.html | 6 ------
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/lib/commons/color/get-background-color.js b/lib/commons/color/get-background-color.js
index 991edf7ed5..c913ecfee6 100644
--- a/lib/commons/color/get-background-color.js
+++ b/lib/commons/color/get-background-color.js
@@ -99,17 +99,18 @@ function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
const bodyStyle = window.getComputedStyle(body);
const htmlBgColor = getOwnBackgroundColor(htmlStyle);
const bodyBgColor = getOwnBackgroundColor(bodyStyle);
- const bodyBgColorApplies = bodyBgColor && visuallyContains(elm, body);
+ const bodyBgColorApplies =
+ bodyBgColor.alpha !== 0 && visuallyContains(elm, body);
if (
- (!htmlBgColor && bodyBgColorApplies) ||
- (bodyBgColorApplies && bodyBgColorApplies.alpha !== 1)
+ (bgColors.alpha !== 0 && htmlBgColor.alpha === 0) ||
+ (bodyBgColorApplies && bodyBgColor.alpha !== 1)
) {
bgColors.unshift(bodyBgColor);
}
if (
- htmlBgColor &&
+ htmlBgColor.alpha !== 0 &&
(!bodyBgColorApplies || (bodyBgColorApplies && bodyBgColor.alpha !== 1))
) {
bgColors.unshift(htmlBgColor);
diff --git a/lib/commons/dom/visually-contains.js b/lib/commons/dom/visually-contains.js
index 3d0b5adf1b..718c72921e 100644
--- a/lib/commons/dom/visually-contains.js
+++ b/lib/commons/dom/visually-contains.js
@@ -49,11 +49,30 @@ function contains(node, parent) {
height: boundingRect.height
};
- if (['scroll', 'auto'].includes(overflow)) {
+ if (
+ ['scroll', 'auto'].includes(overflow) ||
+ parent instanceof window.HTMLHtmlElement
+ ) {
rect.width = parent.scrollWidth;
rect.height = parent.scrollHeight;
}
+ // in Chrome text truncation on the parent will cause the
+ // child to have multiple client rects (one for the bounding
+ // rect of the element and one more for the bounding rect of
+ // the truncation). however this doesn't happen for other
+ // browsers so we'll make it so that if we detect text
+ // truncation and there's only one client rect, we'll use
+ // the bounding rect of the parent as the client rect of
+ // the child
+ if (
+ clientRects.length === 1 &&
+ overflow === 'hidden' &&
+ style.getPropertyValue('white-space') === 'nowrap'
+ ) {
+ clientRects[0] = rect;
+ }
+
// check if any client rect is fully inside the parent rect
// @see https://gist.github.com/Daniel-Hug/d7984d82b58d6d2679a087d896ca3d2b
return clientRects.some(
diff --git a/test/integration/rules/color-contrast/color-contrast.html b/test/integration/rules/color-contrast/color-contrast.html
index f6d27c6e22..e8e4e7e06b 100644
--- a/test/integration/rules/color-contrast/color-contrast.html
+++ b/test/integration/rules/color-contrast/color-contrast.html
@@ -273,9 +273,3 @@
ipsum turpis bibendum ligula, at tempor felis ante non libero.
-
-
-
Some text
-
From 0eb7ac6a609a0ec8c37296d76680c88d2b49b0a7 Mon Sep 17 00:00:00 2001
From: Steven Lambert <2433219+straker@users.noreply.github.com>
Date: Wed, 13 Oct 2021 07:51:39 -0600
Subject: [PATCH 06/10] Update lib/commons/color/get-background-stack.js
Co-authored-by: Wilco Fiers
---
lib/commons/color/get-background-stack.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/commons/color/get-background-stack.js b/lib/commons/color/get-background-stack.js
index 6173a43f7e..d06d071564 100644
--- a/lib/commons/color/get-background-stack.js
+++ b/lib/commons/color/get-background-stack.js
@@ -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);
}
}
From 9fed7f5dc38179e615cb41615acead14781957d0 Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Wed, 13 Oct 2021 08:08:43 -0600
Subject: [PATCH 07/10] fixes
---
lib/commons/color/get-background-color.js | 101 +++++++++++-------
test/commons/color/get-background-color.js | 47 +++-----
test/commons/dom/visually-contains.js | 2 +-
.../rules/color-contrast/color-contrast.html | 24 ++---
4 files changed, 87 insertions(+), 87 deletions(-)
diff --git a/lib/commons/color/get-background-color.js b/lib/commons/color/get-background-color.js
index c913ecfee6..af5ddec0c1 100644
--- a/lib/commons/color/get-background-color.js
+++ b/lib/commons/color/get-background-color.js
@@ -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) {
+ 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,
@@ -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);
});
diff --git a/test/commons/color/get-background-color.js b/test/commons/color/get-background-color.js
index b56daa2def..6770dac8b5 100644
--- a/test/commons/color/get-background-color.js
+++ b/test/commons/color/get-background-color.js
@@ -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;
});
@@ -635,7 +644,6 @@ describe('color.getBackgroundColor', function() {
'style="z-index:-1; position:absolute; width:100%; height:2em; background: #000">' +
'
Some text
';
- var orig = document.body.style.background;
document.body.style.background = '#FFF';
axe.testUtils.flatTreeSetup(fixture);
var actual = axe.commons.color.getBackgroundColor(
@@ -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() {
@@ -659,9 +665,7 @@ describe('color.getBackgroundColor', function() {
'';
- 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(
@@ -670,9 +674,6 @@ describe('color.getBackgroundColor', function() {
);
assert.isNull(actual);
-
- 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() {
@@ -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;
@@ -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() {
@@ -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';
@@ -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() {
@@ -998,7 +995,6 @@ describe('color.getBackgroundColor', function() {
describe('body and document', function() {
it('returns the body background', function() {
fixture.innerHTML = '
elm
';
- var orig = document.body.style.background;
document.body.style.background = '#F00';
axe.testUtils.flatTreeSetup(fixture);
@@ -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);
@@ -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 = '
elm
';
- var orig = document.body.style.background;
document.body.style.background = '#F00';
axe.testUtils.flatTreeSetup(fixture);
@@ -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);
@@ -1036,7 +1029,6 @@ describe('color.getBackgroundColor', function() {
it('returns the html background', function() {
fixture.innerHTML = '';
- var orig = document.documentElement.style.background;
document.documentElement.style.background = '#0F0';
axe.testUtils.flatTreeSetup(fixture);
@@ -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);
@@ -1056,9 +1047,7 @@ describe('color.getBackgroundColor', function() {
it('returns the html background when body does not cover the element', function() {
fixture.innerHTML =
'';
- 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);
@@ -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);
@@ -1078,9 +1065,7 @@ describe('color.getBackgroundColor', function() {
it('returns the body background when body does cover the element', function() {
fixture.innerHTML = '';
- 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);
@@ -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);
@@ -1100,9 +1083,7 @@ describe('color.getBackgroundColor', function() {
it('returns both the html and body background if the body has alpha', function() {
fixture.innerHTML = '';
- 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);
@@ -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);
diff --git a/test/commons/dom/visually-contains.js b/test/commons/dom/visually-contains.js
index 598c3556dd..390322bceb 100644
--- a/test/commons/dom/visually-contains.js
+++ b/test/commons/dom/visually-contains.js
@@ -132,7 +132,7 @@ describe('dom.visuallyContains', function() {
it('should return true for child with truncated text', function() {
var target = queryFixture(
'
' +
- '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.' +
+ '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.' +
'
'
);
var result = axe.commons.dom.visuallyContains(
diff --git a/test/integration/rules/color-contrast/color-contrast.html b/test/integration/rules/color-contrast/color-contrast.html
index e8e4e7e06b..8d3c1d8ece 100644
--- a/test/integration/rules/color-contrast/color-contrast.html
+++ b/test/integration/rules/color-contrast/color-contrast.html
@@ -259,17 +259,17 @@
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 200px; background-color: #FFF"
>
- 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.
From 17f782de58317bc0e9c38552f7e2f1546ed4a428 Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Wed, 13 Oct 2021 10:49:31 -0600
Subject: [PATCH 08/10] split out pr
---
lib/commons/color/get-background-color.js | 65 +-------
lib/commons/color/get-background-stack.js | 42 +++--
test/commons/color/get-background-color.js | 184 +++++++--------------
3 files changed, 79 insertions(+), 212 deletions(-)
diff --git a/lib/commons/color/get-background-color.js b/lib/commons/color/get-background-color.js
index af5ddec0c1..230fa9d568 100644
--- a/lib/commons/color/get-background-color.js
+++ b/lib/commons/color/get-background-color.js
@@ -25,60 +25,6 @@ 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) {
- 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,
@@ -133,16 +79,11 @@ function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
return null;
}
- 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
+ // 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
// 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);
});
diff --git a/lib/commons/color/get-background-stack.js b/lib/commons/color/get-background-stack.js
index d06d071564..cfe0721976 100644
--- a/lib/commons/color/get-background-stack.js
+++ b/lib/commons/color/get-background-stack.js
@@ -69,36 +69,32 @@ function sortPageBackground(elmStack) {
const bodyIndex = elmStack.indexOf(document.body);
const bgNodes = elmStack;
- // 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)
- );
+ // 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
+
if (
- bodyIndex > 1 &&
- htmlBgColor.alpha === 0 &&
- !elementHasImage(document.documentElement)
+ sortBodyElement &&
+ !elementHasImage(document.documentElement) &&
+ getOwnBackgroundColor(window.getComputedStyle(document.documentElement))
+ .alpha === 0
) {
// 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);
- 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);
- }
+ // Put the body background as the lowest element
+ bgNodes.push(document.body);
}
return bgNodes;
}
diff --git a/test/commons/color/get-background-color.js b/test/commons/color/get-background-color.js
index 6770dac8b5..f22ac33fd1 100644
--- a/test/commons/color/get-background-color.js
+++ b/test/commons/color/get-background-color.js
@@ -4,18 +4,9 @@ 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.body.style.background = origBodyBg;
- document.documentElement.style.background = origHtmlBg;
-
+ document.getElementById('fixture').innerHTML = '';
axe.commons.color.incompleteData.clear();
axe._tree = undefined;
});
@@ -644,6 +635,7 @@ describe('color.getBackgroundColor', function() {
'style="z-index:-1; position:absolute; width:100%; height:2em; background: #000">' +
'
Some text
';
+ var orig = document.body.style.background;
document.body.style.background = '#FFF';
axe.testUtils.flatTreeSetup(fixture);
var actual = axe.commons.color.getBackgroundColor(
@@ -657,23 +649,65 @@ 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() {
- fixture.innerHTML =
- '' +
- '';
+ it('returns the body background', function() {
+ fixture.innerHTML = '
elm
';
+ var orig = document.body.style.background;
+ document.body.style.background = '#F00';
+
+ axe.testUtils.flatTreeSetup(fixture);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+
+ it('returns the body background even when the body is MUCH larger than the screen', function() {
+ fixture.innerHTML = '
elm
';
+ var orig = document.body.style.background;
+ document.body.style.background = '#F00';
+
+ axe.testUtils.flatTreeSetup(fixture);
+ var actual = axe.commons.color.getBackgroundColor(
+ document.getElementById('target'),
+ []
+ );
+ 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);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
+ });
+ it('returns the html background', function() {
+ fixture.innerHTML = '';
+ var orig = document.documentElement.style.background;
document.documentElement.style.background = '#0F0';
- document.body.style.background = '#FFF';
+
axe.testUtils.flatTreeSetup(fixture);
var actual = axe.commons.color.getBackgroundColor(
document.getElementById('target'),
[]
);
+ var expected = new axe.commons.color.Color(0, 255, 0, 1);
+ document.documentElement.style.background = orig;
- assert.isNull(actual);
+ assert.closeTo(actual.red, expected.red, 0.5);
+ assert.closeTo(actual.green, expected.green, 0.5);
+ assert.closeTo(actual.blue, expected.blue, 0.5);
+ assert.closeTo(actual.alpha, expected.alpha, 0.1);
});
it('should return background color for inline elements that do not fit the viewport', function() {
@@ -713,7 +747,7 @@ describe('color.getBackgroundColor', function() {
// size body element so that target element is positioned outside of background
var originalHeight = document.body.style.height;
- var originalMargin = document.body.style.margin;
+ var originalBg = document.body.style.background;
document.body.style.height = '1px';
document.body.style.background = '#000';
document.body.style.margin = 0;
@@ -728,7 +762,7 @@ describe('color.getBackgroundColor', function() {
assert.closeTo(actual.alpha, 1, 0);
document.body.style.height = originalHeight;
- document.body.style.margin = originalMargin;
+ document.body.style.background = originalBg;
});
it('should return the html canvas bgColor when element content does not overlap with body', function() {
@@ -737,6 +771,8 @@ 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';
@@ -751,6 +787,8 @@ 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() {
@@ -991,112 +1029,4 @@ describe('color.getBackgroundColor', function() {
assert.closeTo(actual.blue, expected.blue, 0.5);
assert.closeTo(actual.alpha, expected.alpha, 0.1);
});
-
- describe('body and document', function() {
- it('returns the body background', function() {
- fixture.innerHTML = '
elm
';
- document.body.style.background = '#F00';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(255, 0, 0, 1);
-
- assert.closeTo(actual.red, expected.red, 0.5);
- assert.closeTo(actual.green, expected.green, 0.5);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns the body background even when the body is MUCH larger than the screen', function() {
- fixture.innerHTML = '
elm
';
- document.body.style.background = '#F00';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(255, 0, 0, 1);
-
- assert.closeTo(actual.red, expected.red, 0.5);
- assert.closeTo(actual.green, expected.green, 0.5);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns the html background', function() {
- fixture.innerHTML = '';
- document.documentElement.style.background = '#0F0';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(0, 255, 0, 1);
-
- assert.closeTo(actual.red, expected.red, 0.5);
- assert.closeTo(actual.green, expected.green, 0.5);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns the html background when body does not cover the element', function() {
- fixture.innerHTML =
- '';
- document.documentElement.style.background = '#0F0';
- document.body.style.background = '#00F';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(0, 255, 0, 1);
-
- assert.closeTo(actual.red, expected.red, 0.5);
- assert.closeTo(actual.green, expected.green, 0.5);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns the body background when body does cover the element', function() {
- fixture.innerHTML = '';
- document.documentElement.style.background = '#0F0';
- document.body.style.background = '#00F';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(0, 0, 255, 1);
-
- assert.closeTo(actual.red, expected.red, 0.5);
- assert.closeTo(actual.green, expected.green, 0.5);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
-
- it('returns both the html and body background if the body has alpha', function() {
- fixture.innerHTML = '';
- document.documentElement.style.background = '#0F0';
- document.body.style.background = 'rgba(0, 0, 255, 0.5)';
-
- axe.testUtils.flatTreeSetup(fixture);
- var actual = axe.commons.color.getBackgroundColor(
- document.getElementById('target'),
- []
- );
- var expected = new axe.commons.color.Color(0, 128, 128, 1);
-
- assert.closeTo(actual.red, expected.red, 0.5);
- assert.closeTo(actual.green, expected.green, 0.5);
- assert.closeTo(actual.blue, expected.blue, 0.5);
- assert.closeTo(actual.alpha, expected.alpha, 0.1);
- });
- });
});
From 28aa75746a0b5a96e6c509dec340b598a111825f Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Thu, 14 Oct 2021 08:24:13 -0600
Subject: [PATCH 09/10] order
---
lib/commons/dom/visually-contains.js | 70 ++++++++++++++--------------
1 file changed, 34 insertions(+), 36 deletions(-)
diff --git a/lib/commons/dom/visually-contains.js b/lib/commons/dom/visually-contains.js
index 718c72921e..4c120bf5b6 100644
--- a/lib/commons/dom/visually-contains.js
+++ b/lib/commons/dom/visually-contains.js
@@ -1,5 +1,39 @@
import { getNodeFromTree, getScroll } from '../../core/utils';
+/**
+ * Checks whether a parent element visually contains its child, either directly or via scrolling.
+ * Assumes that |parent| is an ancestor of |node|.
+ * @method visuallyContains
+ * @memberof axe.commons.dom
+ * @instance
+ * @param {Element} node
+ * @param {Element} parent
+ * @return {boolean} True if node is visually contained within parent
+ */
+export default function visuallyContains(node, parent) {
+ const parentScrollAncestor = getScrollAncestor(parent);
+
+ // if the elements share a common scroll parent, we can check if the
+ // parent visually contains the node. otherwise we need to check each
+ // scroll parent in between the node and the parent since if the
+ // element is off screen due to the scroll, it won't be visually contained
+ // by the parent
+ do {
+ const nextScrollAncestor = getScrollAncestor(node);
+
+ if (
+ nextScrollAncestor === parentScrollAncestor ||
+ nextScrollAncestor === parent
+ ) {
+ return contains(node, parent);
+ }
+
+ node = nextScrollAncestor;
+ } while (node);
+
+ return false;
+}
+
/**
* Return the ancestor node that is a scroll region.
* @param {VirtualNode}
@@ -85,39 +119,3 @@ function contains(node, parent) {
)
);
}
-
-/**
- * Checks whether a parent element visually contains its child, either directly or via scrolling.
- * Assumes that |parent| is an ancestor of |node|.
- * @method visuallyContains
- * @memberof axe.commons.dom
- * @instance
- * @param {Element} node
- * @param {Element} parent
- * @return {boolean} True if node is visually contained within parent
- */
-function visuallyContains(node, parent) {
- const parentScrollAncestor = getScrollAncestor(parent);
-
- // if the elements share a common scroll parent, we can check if the
- // parent visually contains the node. otherwise we need to check each
- // scroll parent in between the node and the parent since if the
- // element is off screen due to the scroll, it won't be visually contained
- // by the parent
- do {
- const nextScrollAncestor = getScrollAncestor(node);
-
- if (
- nextScrollAncestor === parentScrollAncestor ||
- nextScrollAncestor === parent
- ) {
- return contains(node, parent);
- }
-
- node = nextScrollAncestor;
- } while (node);
-
- return false;
-}
-
-export default visuallyContains;
From 4c774d7e5539de083c3448d6c4befd01f96c7dcb Mon Sep 17 00:00:00 2001
From: Steven Lambert
Date: Fri, 15 Oct 2021 10:42:59 -0600
Subject: [PATCH 10/10] rounding
---
lib/commons/dom/visually-contains.js | 10 ++++++----
test/commons/dom/visually-contains.js | 12 ++++++++++++
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/lib/commons/dom/visually-contains.js b/lib/commons/dom/visually-contains.js
index 4c120bf5b6..c46f8b0fdc 100644
--- a/lib/commons/dom/visually-contains.js
+++ b/lib/commons/dom/visually-contains.js
@@ -112,10 +112,12 @@ function contains(node, parent) {
return clientRects.some(
clientRect =>
!(
- clientRect.left < rect.left ||
- clientRect.top < rect.top ||
- clientRect.left + clientRect.width > rect.left + rect.width ||
- clientRect.top + clientRect.height > rect.top + rect.height
+ Math.ceil(clientRect.left) < Math.floor(rect.left) ||
+ Math.ceil(clientRect.top) < Math.floor(rect.top) ||
+ Math.floor(clientRect.left + clientRect.width) >
+ Math.ceil(rect.left + rect.width) ||
+ Math.floor(clientRect.top + clientRect.height) >
+ Math.ceil(rect.top + rect.height)
)
);
}
diff --git a/test/commons/dom/visually-contains.js b/test/commons/dom/visually-contains.js
index 390322bceb..a43f7bc3b8 100644
--- a/test/commons/dom/visually-contains.js
+++ b/test/commons/dom/visually-contains.js
@@ -154,6 +154,18 @@ describe('dom.visuallyContains', function() {
assert.isFalse(result);
});
+ it('should allow subpixel contains due to rounding', function() {
+ var target = queryFixture(
+ '
' +
+ '
Some text
' +
+ '
'
+ );
+
+ var parent = fixture.querySelector('#parent');
+ var result = axe.commons.dom.visuallyContains(target.actualNode, parent);
+ assert.isTrue(result);
+ });
+
(shadowSupported ? it : xit)(
'should return true when element is visually contained across shadow boundary',
function() {