Skip to content

Commit 639c41b

Browse files
committed
fix(color-contrast): take into account parent opacity for foreground color (#1902)
* fix(color-contrast): take into account parent opacity for foreground color * fix rounding errors * proper closeTo params * update * test recursive
1 parent 77fc838 commit 639c41b

File tree

4 files changed

+81
-7
lines changed

4 files changed

+81
-7
lines changed

lib/checks/color/color-contrast.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (!dom.isVisible(node, false)) {
77
const noScroll = !!(options || {}).noScroll;
88
const bgNodes = [];
99
const bgColor = color.getBackgroundColor(node, bgNodes, noScroll);
10-
const fgColor = color.getForegroundColor(node, noScroll);
10+
const fgColor = color.getForegroundColor(node, noScroll, bgColor);
1111

1212
const nodeStyle = window.getComputedStyle(node);
1313
const fontSize = parseFloat(nodeStyle.getPropertyValue('font-size'));

lib/commons/color/get-foreground-color.js

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
/* global axe, color */
22

3+
function getOpacity(node) {
4+
if (!node) {
5+
return 1;
6+
}
7+
8+
const vNode = axe.utils.getNodeFromTree(node);
9+
10+
if (vNode && vNode._opacity !== undefined && vNode._opacity !== null) {
11+
return vNode._opacity;
12+
}
13+
14+
const nodeStyle = window.getComputedStyle(node);
15+
const opacity = nodeStyle.getPropertyValue('opacity');
16+
const finalOpacity = opacity * getOpacity(node.parentElement);
17+
18+
// cache the results of the getOpacity check on the parent tree
19+
// so we don't have to look at the parent tree again for all its
20+
// descendants
21+
if (vNode) {
22+
vNode._opacity = finalOpacity;
23+
}
24+
25+
return finalOpacity;
26+
}
27+
328
/**
429
* Returns the flattened foreground color of an element, or null if it can't be determined because
530
* of transparency
@@ -8,22 +33,26 @@
833
* @instance
934
* @param {Element} node
1035
* @param {Boolean} noScroll (default false)
36+
* @param {Color} bgColor
1137
* @return {Color|null}
1238
*/
13-
color.getForegroundColor = function(node, noScroll) {
14-
var nodeStyle = window.getComputedStyle(node);
39+
color.getForegroundColor = function(node, noScroll, bgColor) {
40+
const nodeStyle = window.getComputedStyle(node);
1541

16-
var fgColor = new color.Color();
42+
const fgColor = new color.Color();
1743
fgColor.parseRgbString(nodeStyle.getPropertyValue('color'));
18-
var opacity = nodeStyle.getPropertyValue('opacity');
44+
const opacity = getOpacity(node);
1945
fgColor.alpha = fgColor.alpha * opacity;
2046
if (fgColor.alpha === 1) {
2147
return fgColor;
2248
}
2349

24-
var bgColor = color.getBackgroundColor(node, [], noScroll);
50+
if (!bgColor) {
51+
bgColor = color.getBackgroundColor(node, [], noScroll);
52+
}
53+
2554
if (bgColor === null) {
26-
var reason = axe.commons.color.incompleteData.get('bgColor');
55+
const reason = axe.commons.color.incompleteData.get('bgColor');
2756
axe.commons.color.incompleteData.set('fgColor', reason);
2857
return null;
2958
}

test/commons/color/get-foreground-color.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* global sinon */
2+
13
describe('color.getForegroundColor', function() {
24
'use strict';
35

@@ -42,6 +44,39 @@ describe('color.getForegroundColor', function() {
4244
assert.equal(actual.alpha, expected.alpha);
4345
});
4446

47+
it('should take into account parent opacity tree', function() {
48+
fixture.innerHTML =
49+
'<div style="background-color: #fafafa">' +
50+
'<div style="height: 40px; width: 30px; opacity: 0.6">' +
51+
'<div id="target" style="height: 20px; width: 15px; color: rgba(0, 0, 0, 0.87);">' +
52+
'This is my text' +
53+
'</div></div></div>';
54+
var target = fixture.querySelector('#target');
55+
var actual = axe.commons.color.getForegroundColor(target);
56+
var expected = new axe.commons.color.Color(119.5, 119.5, 119.5, 1);
57+
assert.closeTo(actual.red, expected.red, 0.8);
58+
assert.closeTo(actual.green, expected.green, 0.8);
59+
assert.closeTo(actual.blue, expected.blue, 0.8);
60+
assert.closeTo(actual.alpha, expected.alpha, 0.1);
61+
});
62+
63+
it('should take into account entire parent opacity tree', function() {
64+
fixture.innerHTML =
65+
'<div style="background-color: #fafafa">' +
66+
'<div style="height: 40px; width: 30px; opacity: 0.75">' +
67+
'<div style="height: 40px; width: 30px; opacity: 0.8">' +
68+
'<div id="target" style="height: 20px; width: 15px; color: rgba(0, 0, 0, 0.87);">' +
69+
'This is my text' +
70+
'</div></div></div></div>';
71+
var target = fixture.querySelector('#target');
72+
var actual = axe.commons.color.getForegroundColor(target);
73+
var expected = new axe.commons.color.Color(119.5, 119.5, 119.5, 1);
74+
assert.closeTo(actual.red, expected.red, 0.8);
75+
assert.closeTo(actual.green, expected.green, 0.8);
76+
assert.closeTo(actual.blue, expected.blue, 0.8);
77+
assert.closeTo(actual.alpha, expected.alpha, 0.1);
78+
});
79+
4580
it('should return null if containing parent has a background image and is non-opaque', function() {
4681
fixture.innerHTML =
4782
'<div style="height: 40px; width: 30px;' +
@@ -68,6 +103,15 @@ describe('color.getForegroundColor', function() {
68103
assert.equal(actual.alpha, expected.alpha);
69104
});
70105

106+
it('should not recalculate bgColor if passed in', function() {
107+
var spy = sinon.spy(axe.commons.color, 'getBackgroundColor');
108+
var bgColor = new axe.commons.color.Color(255, 255, 255, 1);
109+
var node = document.createElement('div');
110+
axe.commons.color.getForegroundColor(node, false, bgColor);
111+
assert.isFalse(spy.called);
112+
spy.restore();
113+
});
114+
71115
(shadowSupported ? it : xit)(
72116
'should return the fgcolor from inside of Shadow DOM',
73117
function() {

test/runner.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
88
<script src="/node_modules/mocha/mocha.js"></script>
99
<script src="/node_modules/chai/chai.js"></script>
10+
<script src="/node_modules/sinon/pkg/sinon.js"></script>
1011
<style>
1112
/* This is essential to stop mocha reporter created anchors from making the document overflow, mainly along the x-axis */
1213
#mocha h1 a {

0 commit comments

Comments
 (0)