@@ -17,157 +17,161 @@ import {
1717import { memoize } from '../../core/utils' ;
1818
1919export 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+ }
3637
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+ }
4243
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
6862 } ) ;
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+ }
6974
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+ }
7384
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+ }
83106
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 ;
104118 }
105- }
106119
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 ;
119123
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+ }
123131
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+ }
131140
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+ } ) ;
140152
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+ }
165165
166- if ( ! isValid ) {
167- this . relatedNodes ( bgNodes ) ;
168- }
166+ if ( ! isValid ) {
167+ this . relatedNodes ( bgNodes ) ;
168+ }
169169
170- return isValid ;
170+ return isValid ;
171+ } catch ( err ) {
172+ a11yEngine . axeErrorHandlers . addCheckError ( 'axe-color-contrast-check' , err ) ;
173+ return undefined ;
174+ }
171175}
172176
173177function findPseudoElement (
0 commit comments