@@ -17,157 +17,161 @@ import {
17
17
import { memoize } from '../../core/utils' ;
18
18
19
19
export default function colorContrastEvaluate ( node , options , virtualNode ) {
20
- const {
21
- ignoreUnicode,
22
- ignoreLength,
23
- ignorePseudo,
24
- boldValue,
25
- boldTextPt,
26
- largeTextPt,
27
- contrastRatio,
28
- shadowOutlineEmMax,
29
- pseudoSizeThreshold
30
- } = options ;
31
-
32
- if ( ! isVisibleOnScreen ( node ) ) {
33
- this . data ( { messageKey : 'hidden' } ) ;
34
- return true ;
35
- }
20
+ try {
21
+ const {
22
+ ignoreUnicode,
23
+ ignoreLength,
24
+ ignorePseudo,
25
+ boldValue,
26
+ boldTextPt,
27
+ largeTextPt,
28
+ contrastRatio,
29
+ shadowOutlineEmMax,
30
+ pseudoSizeThreshold
31
+ } = options ;
32
+
33
+ if ( ! isVisibleOnScreen ( node ) ) {
34
+ this . data ( { messageKey : 'hidden' } ) ;
35
+ return true ;
36
+ }
36
37
37
- const visibleText = visibleVirtual ( virtualNode , false , true ) ;
38
- if ( ignoreUnicode && textIsEmojis ( visibleText ) ) {
39
- this . data ( { messageKey : 'nonBmp' } ) ;
40
- return undefined ;
41
- }
38
+ const visibleText = visibleVirtual ( virtualNode , false , true ) ;
39
+ if ( ignoreUnicode && textIsEmojis ( visibleText ) ) {
40
+ this . data ( { messageKey : 'nonBmp' } ) ;
41
+ return undefined ;
42
+ }
42
43
43
- const nodeStyle = window . getComputedStyle ( node ) ;
44
- const fontSize = parseFloat ( nodeStyle . getPropertyValue ( 'font-size' ) ) ;
45
- const fontWeight = nodeStyle . getPropertyValue ( 'font-weight' ) ;
46
- const bold = parseFloat ( fontWeight ) >= boldValue || fontWeight === 'bold' ;
47
-
48
- const ptSize = Math . ceil ( fontSize * 72 ) / 96 ;
49
- const isSmallFont =
50
- ( bold && ptSize < boldTextPt ) || ( ! bold && ptSize < largeTextPt ) ;
51
-
52
- const { expected, minThreshold, maxThreshold } = isSmallFont
53
- ? contrastRatio . normal
54
- : contrastRatio . large ;
55
-
56
- // if element or a parent has pseudo content then we need to mark
57
- // as needs review
58
- const pseudoElm = findPseudoElement ( virtualNode , {
59
- ignorePseudo,
60
- pseudoSizeThreshold
61
- } ) ;
62
- if ( pseudoElm ) {
63
- this . data ( {
64
- fontSize : `${ ( ( fontSize * 72 ) / 96 ) . toFixed ( 1 ) } pt (${ fontSize } px)` ,
65
- fontWeight : bold ? 'bold' : 'normal' ,
66
- messageKey : 'pseudoContent' ,
67
- expectedContrastRatio : expected + ':1'
44
+ const nodeStyle = window . getComputedStyle ( node ) ;
45
+ const fontSize = parseFloat ( nodeStyle . getPropertyValue ( 'font-size' ) ) ;
46
+ const fontWeight = nodeStyle . getPropertyValue ( 'font-weight' ) ;
47
+ const bold = parseFloat ( fontWeight ) >= boldValue || fontWeight === 'bold' ;
48
+
49
+ const ptSize = Math . ceil ( fontSize * 72 ) / 96 ;
50
+ const isSmallFont =
51
+ ( bold && ptSize < boldTextPt ) || ( ! bold && ptSize < largeTextPt ) ;
52
+
53
+ const { expected, minThreshold, maxThreshold } = isSmallFont
54
+ ? contrastRatio . normal
55
+ : contrastRatio . large ;
56
+
57
+ // if element or a parent has pseudo content then we need to mark
58
+ // as needs review
59
+ const pseudoElm = findPseudoElement ( virtualNode , {
60
+ ignorePseudo,
61
+ pseudoSizeThreshold
68
62
} ) ;
63
+ if ( pseudoElm ) {
64
+ this . data ( {
65
+ fontSize : `${ ( ( fontSize * 72 ) / 96 ) . toFixed ( 1 ) } pt (${ fontSize } px)` ,
66
+ fontWeight : bold ? 'bold' : 'normal' ,
67
+ messageKey : 'pseudoContent' ,
68
+ expectedContrastRatio : expected + ':1'
69
+ } ) ;
70
+
71
+ this . relatedNodes ( pseudoElm . actualNode ) ;
72
+ return undefined ;
73
+ }
69
74
70
- this . relatedNodes ( pseudoElm . actualNode ) ;
71
- return undefined ;
72
- }
75
+ // Thin shadows only. Thicker shadows are included in the background instead
76
+ const shadowColors = getTextShadowColors ( node , {
77
+ minRatio : 0.001 ,
78
+ maxRatio : shadowOutlineEmMax
79
+ } ) ;
80
+ if ( shadowColors === null ) {
81
+ this . data ( { messageKey : 'complexTextShadows' } ) ;
82
+ return undefined ;
83
+ }
73
84
74
- // Thin shadows only. Thicker shadows are included in the background instead
75
- const shadowColors = getTextShadowColors ( node , {
76
- minRatio : 0.001 ,
77
- maxRatio : shadowOutlineEmMax
78
- } ) ;
79
- if ( shadowColors === null ) {
80
- this . data ( { messageKey : 'complexTextShadows' } ) ;
81
- return undefined ;
82
- }
85
+ const bgNodes = [ ] ;
86
+ const bgColor = getBackgroundColor ( node , bgNodes , shadowOutlineEmMax ) ;
87
+ const fgColor = getForegroundColor ( node , false , bgColor , options ) ;
88
+
89
+ let contrast = null ;
90
+ let contrastContributor = null ;
91
+ let shadowColor = null ;
92
+ if ( shadowColors . length === 0 ) {
93
+ contrast = getContrast ( bgColor , fgColor ) ;
94
+ } else if ( fgColor && bgColor ) {
95
+ shadowColor = [ ...shadowColors , bgColor ] . reduce ( flattenShadowColors ) ;
96
+ // Compare shadow, bgColor, textColor. Check passes if any is sufficient
97
+ const fgBgContrast = getContrast ( bgColor , fgColor ) ;
98
+ const bgShContrast = getContrast ( bgColor , shadowColor ) ;
99
+ const fgShContrast = getContrast ( shadowColor , fgColor ) ;
100
+ contrast = Math . max ( fgBgContrast , bgShContrast , fgShContrast ) ;
101
+ if ( contrast !== fgBgContrast ) {
102
+ contrastContributor =
103
+ bgShContrast > fgShContrast ? 'shadowOnBgColor' : 'fgOnShadowColor' ;
104
+ }
105
+ }
83
106
84
- const bgNodes = [ ] ;
85
- axe . _cache . set ( 'ruleId' , 'axe-color-contrast' ) ;
86
- const bgColor = getBackgroundColor ( node , bgNodes , shadowOutlineEmMax ) ;
87
- const fgColor = getForegroundColor ( node , false , bgColor , options ) ;
88
-
89
- let contrast = null ;
90
- let contrastContributor = null ;
91
- let shadowColor = null ;
92
- if ( shadowColors . length === 0 ) {
93
- contrast = getContrast ( bgColor , fgColor ) ;
94
- } else if ( fgColor && bgColor ) {
95
- shadowColor = [ ...shadowColors , bgColor ] . reduce ( flattenShadowColors ) ;
96
- // Compare shadow, bgColor, textColor. Check passes if any is sufficient
97
- const fgBgContrast = getContrast ( bgColor , fgColor ) ;
98
- const bgShContrast = getContrast ( bgColor , shadowColor ) ;
99
- const fgShContrast = getContrast ( shadowColor , fgColor ) ;
100
- contrast = Math . max ( fgBgContrast , bgShContrast , fgShContrast ) ;
101
- if ( contrast !== fgBgContrast ) {
102
- contrastContributor =
103
- bgShContrast > fgShContrast ? 'shadowOnBgColor' : 'fgOnShadowColor' ;
107
+ const isValid = contrast > expected ;
108
+
109
+ // ratio is outside range
110
+ if (
111
+ ( typeof minThreshold === 'number' &&
112
+ ( typeof contrast !== 'number' || contrast < minThreshold ) ) ||
113
+ ( typeof maxThreshold === 'number' &&
114
+ ( typeof contrast !== 'number' || contrast > maxThreshold ) )
115
+ ) {
116
+ this . data ( { contrastRatio : contrast } ) ;
117
+ return true ;
104
118
}
105
- }
106
119
107
- const isValid = contrast > expected ;
108
-
109
- // ratio is outside range
110
- if (
111
- ( typeof minThreshold === 'number' &&
112
- ( typeof contrast !== 'number' || contrast < minThreshold ) ) ||
113
- ( typeof maxThreshold === 'number' &&
114
- ( typeof contrast !== 'number' || contrast > maxThreshold ) )
115
- ) {
116
- this . data ( { contrastRatio : contrast } ) ;
117
- return true ;
118
- }
120
+ // truncate ratio to three digits while rounding down
121
+ // 4.499 = 4.49, 4.019 = 4.01
122
+ const truncatedResult = Math . floor ( contrast * 100 ) / 100 ;
119
123
120
- // truncate ratio to three digits while rounding down
121
- // 4.499 = 4.49, 4.019 = 4.01
122
- const truncatedResult = Math . floor ( contrast * 100 ) / 100 ;
124
+ // if fgColor or bgColor are missing, get more information.
125
+ let missing ;
126
+ if ( bgColor === null ) {
127
+ missing = incompleteData . get ( 'bgColor' ) ;
128
+ } else if ( ! isValid ) {
129
+ missing = contrastContributor ;
130
+ }
123
131
124
- // if fgColor or bgColor are missing, get more information.
125
- let missing ;
126
- if ( bgColor === null ) {
127
- missing = incompleteData . get ( 'bgColor' ) ;
128
- } else if ( ! isValid ) {
129
- missing = contrastContributor ;
130
- }
132
+ const equalRatio = truncatedResult === 1 ;
133
+ const shortTextContent = visibleText . length === 1 ;
134
+ if ( equalRatio ) {
135
+ missing = incompleteData . set ( 'bgColor' , 'equalRatio' ) ;
136
+ } else if ( ! isValid && shortTextContent && ! ignoreLength ) {
137
+ // Check that the text content is a single character long
138
+ missing = 'shortTextContent' ;
139
+ }
131
140
132
- const equalRatio = truncatedResult === 1 ;
133
- const shortTextContent = visibleText . length === 1 ;
134
- if ( equalRatio ) {
135
- missing = incompleteData . set ( 'bgColor' , 'equalRatio' ) ;
136
- } else if ( ! isValid && shortTextContent && ! ignoreLength ) {
137
- // Check that the text content is a single character long
138
- missing = 'shortTextContent' ;
139
- }
141
+ // need both independently in case both are missing
142
+ this . data ( {
143
+ fgColor : fgColor ? fgColor . toHexString ( ) : undefined ,
144
+ bgColor : bgColor ? bgColor . toHexString ( ) : undefined ,
145
+ contrastRatio : truncatedResult ,
146
+ fontSize : `${ ( ( fontSize * 72 ) / 96 ) . toFixed ( 1 ) } pt (${ fontSize } px)` ,
147
+ fontWeight : bold ? 'bold' : 'normal' ,
148
+ messageKey : missing ,
149
+ expectedContrastRatio : expected + ':1' ,
150
+ shadowColor : shadowColor ? shadowColor . toHexString ( ) : undefined
151
+ } ) ;
140
152
141
- // need both independently in case both are missing
142
- this . data ( {
143
- fgColor : fgColor ? fgColor . toHexString ( ) : undefined ,
144
- bgColor : bgColor ? bgColor . toHexString ( ) : undefined ,
145
- contrastRatio : truncatedResult ,
146
- fontSize : `${ ( ( fontSize * 72 ) / 96 ) . toFixed ( 1 ) } pt (${ fontSize } px)` ,
147
- fontWeight : bold ? 'bold' : 'normal' ,
148
- messageKey : missing ,
149
- expectedContrastRatio : expected + ':1' ,
150
- shadowColor : shadowColor ? shadowColor . toHexString ( ) : undefined
151
- } ) ;
152
-
153
- // We don't know, so we'll put it into Can't Tell
154
- if (
155
- fgColor === null ||
156
- bgColor === null ||
157
- equalRatio ||
158
- ( shortTextContent && ! ignoreLength && ! isValid )
159
- ) {
160
- missing = null ;
161
- incompleteData . clear ( ) ;
162
- this . relatedNodes ( bgNodes ) ;
163
- return undefined ;
164
- }
153
+ // We don't know, so we'll put it into Can't Tell
154
+ if (
155
+ fgColor === null ||
156
+ bgColor === null ||
157
+ equalRatio ||
158
+ ( shortTextContent && ! ignoreLength && ! isValid )
159
+ ) {
160
+ missing = null ;
161
+ incompleteData . clear ( ) ;
162
+ this . relatedNodes ( bgNodes ) ;
163
+ return undefined ;
164
+ }
165
165
166
- if ( ! isValid ) {
167
- this . relatedNodes ( bgNodes ) ;
168
- }
166
+ if ( ! isValid ) {
167
+ this . relatedNodes ( bgNodes ) ;
168
+ }
169
169
170
- return isValid ;
170
+ return isValid ;
171
+ } catch ( err ) {
172
+ a11yEngine . axeErrorHandlers . addCheckError ( 'axe-color-contrast-check' , err ) ;
173
+ return undefined ;
174
+ }
171
175
}
172
176
173
177
function findPseudoElement (
0 commit comments