@@ -17,156 +17,162 @@ 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
- const bgColor = getBackgroundColor ( node , bgNodes , shadowOutlineEmMax ) ;
86
- const fgColor = getForegroundColor ( node , false , bgColor , options ) ;
87
-
88
- let contrast = null ;
89
- let contrastContributor = null ;
90
- let shadowColor = null ;
91
- if ( shadowColors . length === 0 ) {
92
- contrast = getContrast ( bgColor , fgColor ) ;
93
- } else if ( fgColor && bgColor ) {
94
- shadowColor = [ ...shadowColors , bgColor ] . reduce ( flattenShadowColors ) ;
95
- // Compare shadow, bgColor, textColor. Check passes if any is sufficient
96
- const fgBgContrast = getContrast ( bgColor , fgColor ) ;
97
- const bgShContrast = getContrast ( bgColor , shadowColor ) ;
98
- const fgShContrast = getContrast ( shadowColor , fgColor ) ;
99
- contrast = Math . max ( fgBgContrast , bgShContrast , fgShContrast ) ;
100
- if ( contrast !== fgBgContrast ) {
101
- contrastContributor =
102
- 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 ;
103
118
}
104
- }
105
119
106
- const isValid = contrast > expected ;
107
-
108
- // ratio is outside range
109
- if (
110
- ( typeof minThreshold === 'number' &&
111
- ( typeof contrast !== 'number' || contrast < minThreshold ) ) ||
112
- ( typeof maxThreshold === 'number' &&
113
- ( typeof contrast !== 'number' || contrast > maxThreshold ) )
114
- ) {
115
- this . data ( { contrastRatio : contrast } ) ;
116
- return true ;
117
- }
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 ;
118
123
119
- // truncate ratio to three digits while rounding down
120
- // 4.499 = 4.49, 4.019 = 4.01
121
- 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
+ }
122
131
123
- // if fgColor or bgColor are missing, get more information.
124
- let missing ;
125
- if ( bgColor === null ) {
126
- missing = incompleteData . get ( 'bgColor' ) ;
127
- } else if ( ! isValid ) {
128
- missing = contrastContributor ;
129
- }
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
+ }
130
140
131
- const equalRatio = truncatedResult === 1 ;
132
- const shortTextContent = visibleText . length === 1 ;
133
- if ( equalRatio ) {
134
- missing = incompleteData . set ( 'bgColor' , 'equalRatio' ) ;
135
- } else if ( ! isValid && shortTextContent && ! ignoreLength ) {
136
- // Check that the text content is a single character long
137
- missing = 'shortTextContent' ;
138
- }
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
+ } ) ;
139
152
140
- // need both independently in case both are missing
141
- this . data ( {
142
- fgColor : fgColor ? fgColor . toHexString ( ) : undefined ,
143
- bgColor : bgColor ? bgColor . toHexString ( ) : undefined ,
144
- contrastRatio : truncatedResult ,
145
- fontSize : `${ ( ( fontSize * 72 ) / 96 ) . toFixed ( 1 ) } pt (${ fontSize } px)` ,
146
- fontWeight : bold ? 'bold' : 'normal' ,
147
- messageKey : missing ,
148
- expectedContrastRatio : expected + ':1' ,
149
- shadowColor : shadowColor ? shadowColor . toHexString ( ) : undefined
150
- } ) ;
151
-
152
- // We don't know, so we'll put it into Can't Tell
153
- if (
154
- fgColor === null ||
155
- bgColor === null ||
156
- equalRatio ||
157
- ( shortTextContent && ! ignoreLength && ! isValid )
158
- ) {
159
- missing = null ;
160
- incompleteData . clear ( ) ;
161
- this . relatedNodes ( bgNodes ) ;
162
- return undefined ;
163
- }
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
+ }
164
165
165
- if ( ! isValid ) {
166
- this . relatedNodes ( bgNodes ) ;
167
- }
166
+ if ( ! isValid ) {
167
+ this . relatedNodes ( bgNodes ) ;
168
+ }
168
169
169
- return isValid ;
170
+ return isValid ;
171
+ } catch ( err ) {
172
+ this . data ( { messageKey : 'Check error' } ) ;
173
+ a11yEngine . errorHandler . addCheckError ( options ?. ruleId , err ) ;
174
+ return undefined ;
175
+ }
170
176
}
171
177
172
178
function findPseudoElement (
0 commit comments