diff --git a/lib/checks/color/color-contrast-enhanced.json b/lib/checks/color/color-contrast-enhanced.json index b09a3269..e14d8eb1 100644 --- a/lib/checks/color/color-contrast-enhanced.json +++ b/lib/checks/color/color-contrast-enhanced.json @@ -20,7 +20,8 @@ }, "pseudoSizeThreshold": 0.25, "shadowOutlineEmMax": 0.1, - "textStrokeEmMin": 0.03 + "textStrokeEmMin": 0.03, + "ruleId": "color-contrast-enhanced" }, "metadata": { "impact": "serious", diff --git a/lib/checks/color/color-contrast-evaluate.js b/lib/checks/color/color-contrast-evaluate.js index 8916bd09..4ccbbe33 100644 --- a/lib/checks/color/color-contrast-evaluate.js +++ b/lib/checks/color/color-contrast-evaluate.js @@ -17,156 +17,162 @@ import { import { memoize } from '../../core/utils'; export default function colorContrastEvaluate(node, options, virtualNode) { - const { - ignoreUnicode, - ignoreLength, - ignorePseudo, - boldValue, - boldTextPt, - largeTextPt, - contrastRatio, - shadowOutlineEmMax, - pseudoSizeThreshold - } = options; - - if (!isVisibleOnScreen(node)) { - this.data({ messageKey: 'hidden' }); - return true; - } + try { + const { + ignoreUnicode, + ignoreLength, + ignorePseudo, + boldValue, + boldTextPt, + largeTextPt, + contrastRatio, + shadowOutlineEmMax, + pseudoSizeThreshold + } = options; + + if (!isVisibleOnScreen(node)) { + this.data({ messageKey: 'hidden' }); + return true; + } - const visibleText = visibleVirtual(virtualNode, false, true); - if (ignoreUnicode && textIsEmojis(visibleText)) { - this.data({ messageKey: 'nonBmp' }); - return undefined; - } + const visibleText = visibleVirtual(virtualNode, false, true); + if (ignoreUnicode && textIsEmojis(visibleText)) { + this.data({ messageKey: 'nonBmp' }); + return undefined; + } - const nodeStyle = window.getComputedStyle(node); - const fontSize = parseFloat(nodeStyle.getPropertyValue('font-size')); - const fontWeight = nodeStyle.getPropertyValue('font-weight'); - const bold = parseFloat(fontWeight) >= boldValue || fontWeight === 'bold'; - - const ptSize = Math.ceil(fontSize * 72) / 96; - const isSmallFont = - (bold && ptSize < boldTextPt) || (!bold && ptSize < largeTextPt); - - const { expected, minThreshold, maxThreshold } = isSmallFont - ? contrastRatio.normal - : contrastRatio.large; - - // if element or a parent has pseudo content then we need to mark - // as needs review - const pseudoElm = findPseudoElement(virtualNode, { - ignorePseudo, - pseudoSizeThreshold - }); - if (pseudoElm) { - this.data({ - fontSize: `${((fontSize * 72) / 96).toFixed(1)}pt (${fontSize}px)`, - fontWeight: bold ? 'bold' : 'normal', - messageKey: 'pseudoContent', - expectedContrastRatio: expected + ':1' + const nodeStyle = window.getComputedStyle(node); + const fontSize = parseFloat(nodeStyle.getPropertyValue('font-size')); + const fontWeight = nodeStyle.getPropertyValue('font-weight'); + const bold = parseFloat(fontWeight) >= boldValue || fontWeight === 'bold'; + + const ptSize = Math.ceil(fontSize * 72) / 96; + const isSmallFont = + (bold && ptSize < boldTextPt) || (!bold && ptSize < largeTextPt); + + const { expected, minThreshold, maxThreshold } = isSmallFont + ? contrastRatio.normal + : contrastRatio.large; + + // if element or a parent has pseudo content then we need to mark + // as needs review + const pseudoElm = findPseudoElement(virtualNode, { + ignorePseudo, + pseudoSizeThreshold }); + if (pseudoElm) { + this.data({ + fontSize: `${((fontSize * 72) / 96).toFixed(1)}pt (${fontSize}px)`, + fontWeight: bold ? 'bold' : 'normal', + messageKey: 'pseudoContent', + expectedContrastRatio: expected + ':1' + }); + + this.relatedNodes(pseudoElm.actualNode); + return undefined; + } - this.relatedNodes(pseudoElm.actualNode); - return undefined; - } + // Thin shadows only. Thicker shadows are included in the background instead + const shadowColors = getTextShadowColors(node, { + minRatio: 0.001, + maxRatio: shadowOutlineEmMax + }); + if (shadowColors === null) { + this.data({ messageKey: 'complexTextShadows' }); + return undefined; + } - // Thin shadows only. Thicker shadows are included in the background instead - const shadowColors = getTextShadowColors(node, { - minRatio: 0.001, - maxRatio: shadowOutlineEmMax - }); - if (shadowColors === null) { - this.data({ messageKey: 'complexTextShadows' }); - return undefined; - } + const bgNodes = []; + const bgColor = getBackgroundColor(node, bgNodes, shadowOutlineEmMax); + const fgColor = getForegroundColor(node, false, bgColor, options); + + let contrast = null; + let contrastContributor = null; + let shadowColor = null; + if (shadowColors.length === 0) { + contrast = getContrast(bgColor, fgColor); + } else if (fgColor && bgColor) { + shadowColor = [...shadowColors, bgColor].reduce(flattenShadowColors); + // Compare shadow, bgColor, textColor. Check passes if any is sufficient + const fgBgContrast = getContrast(bgColor, fgColor); + const bgShContrast = getContrast(bgColor, shadowColor); + const fgShContrast = getContrast(shadowColor, fgColor); + contrast = Math.max(fgBgContrast, bgShContrast, fgShContrast); + if (contrast !== fgBgContrast) { + contrastContributor = + bgShContrast > fgShContrast ? 'shadowOnBgColor' : 'fgOnShadowColor'; + } + } - const bgNodes = []; - const bgColor = getBackgroundColor(node, bgNodes, shadowOutlineEmMax); - const fgColor = getForegroundColor(node, false, bgColor, options); - - let contrast = null; - let contrastContributor = null; - let shadowColor = null; - if (shadowColors.length === 0) { - contrast = getContrast(bgColor, fgColor); - } else if (fgColor && bgColor) { - shadowColor = [...shadowColors, bgColor].reduce(flattenShadowColors); - // Compare shadow, bgColor, textColor. Check passes if any is sufficient - const fgBgContrast = getContrast(bgColor, fgColor); - const bgShContrast = getContrast(bgColor, shadowColor); - const fgShContrast = getContrast(shadowColor, fgColor); - contrast = Math.max(fgBgContrast, bgShContrast, fgShContrast); - if (contrast !== fgBgContrast) { - contrastContributor = - bgShContrast > fgShContrast ? 'shadowOnBgColor' : 'fgOnShadowColor'; + const isValid = contrast > expected; + + // ratio is outside range + if ( + (typeof minThreshold === 'number' && + (typeof contrast !== 'number' || contrast < minThreshold)) || + (typeof maxThreshold === 'number' && + (typeof contrast !== 'number' || contrast > maxThreshold)) + ) { + this.data({ contrastRatio: contrast }); + return true; } - } - const isValid = contrast > expected; - - // ratio is outside range - if ( - (typeof minThreshold === 'number' && - (typeof contrast !== 'number' || contrast < minThreshold)) || - (typeof maxThreshold === 'number' && - (typeof contrast !== 'number' || contrast > maxThreshold)) - ) { - this.data({ contrastRatio: contrast }); - return true; - } + // truncate ratio to three digits while rounding down + // 4.499 = 4.49, 4.019 = 4.01 + const truncatedResult = Math.floor(contrast * 100) / 100; - // truncate ratio to three digits while rounding down - // 4.499 = 4.49, 4.019 = 4.01 - const truncatedResult = Math.floor(contrast * 100) / 100; + // if fgColor or bgColor are missing, get more information. + let missing; + if (bgColor === null) { + missing = incompleteData.get('bgColor'); + } else if (!isValid) { + missing = contrastContributor; + } - // if fgColor or bgColor are missing, get more information. - let missing; - if (bgColor === null) { - missing = incompleteData.get('bgColor'); - } else if (!isValid) { - missing = contrastContributor; - } + const equalRatio = truncatedResult === 1; + const shortTextContent = visibleText.length === 1; + if (equalRatio) { + missing = incompleteData.set('bgColor', 'equalRatio'); + } else if (!isValid && shortTextContent && !ignoreLength) { + // Check that the text content is a single character long + missing = 'shortTextContent'; + } - const equalRatio = truncatedResult === 1; - const shortTextContent = visibleText.length === 1; - if (equalRatio) { - missing = incompleteData.set('bgColor', 'equalRatio'); - } else if (!isValid && shortTextContent && !ignoreLength) { - // Check that the text content is a single character long - missing = 'shortTextContent'; - } + // need both independently in case both are missing + this.data({ + fgColor: fgColor ? fgColor.toHexString() : undefined, + bgColor: bgColor ? bgColor.toHexString() : undefined, + contrastRatio: truncatedResult, + fontSize: `${((fontSize * 72) / 96).toFixed(1)}pt (${fontSize}px)`, + fontWeight: bold ? 'bold' : 'normal', + messageKey: missing, + expectedContrastRatio: expected + ':1', + shadowColor: shadowColor ? shadowColor.toHexString() : undefined + }); - // need both independently in case both are missing - this.data({ - fgColor: fgColor ? fgColor.toHexString() : undefined, - bgColor: bgColor ? bgColor.toHexString() : undefined, - contrastRatio: truncatedResult, - fontSize: `${((fontSize * 72) / 96).toFixed(1)}pt (${fontSize}px)`, - fontWeight: bold ? 'bold' : 'normal', - messageKey: missing, - expectedContrastRatio: expected + ':1', - shadowColor: shadowColor ? shadowColor.toHexString() : undefined - }); - - // We don't know, so we'll put it into Can't Tell - if ( - fgColor === null || - bgColor === null || - equalRatio || - (shortTextContent && !ignoreLength && !isValid) - ) { - missing = null; - incompleteData.clear(); - this.relatedNodes(bgNodes); - return undefined; - } + // We don't know, so we'll put it into Can't Tell + if ( + fgColor === null || + bgColor === null || + equalRatio || + (shortTextContent && !ignoreLength && !isValid) + ) { + missing = null; + incompleteData.clear(); + this.relatedNodes(bgNodes); + return undefined; + } - if (!isValid) { - this.relatedNodes(bgNodes); - } + if (!isValid) { + this.relatedNodes(bgNodes); + } - return isValid; + return isValid; + } catch (err) { + this.data({ messageKey: 'Check error' }); + a11yEngine.errorHandler.addCheckError(options?.ruleId, err); + return undefined; + } } function findPseudoElement( diff --git a/lib/checks/color/color-contrast.json b/lib/checks/color/color-contrast.json index 135eeae4..dd5b1f5b 100644 --- a/lib/checks/color/color-contrast.json +++ b/lib/checks/color/color-contrast.json @@ -18,7 +18,8 @@ }, "pseudoSizeThreshold": 0.25, "shadowOutlineEmMax": 0.2, - "textStrokeEmMin": 0.03 + "textStrokeEmMin": 0.03, + "ruleId": "color-contrast" }, "metadata": { "impact": "serious", diff --git a/lib/checks/forms/autocomplete-a11y-evaluate.js b/lib/checks/forms/autocomplete-a11y-evaluate.js index b635c9bf..11d4863e 100644 --- a/lib/checks/forms/autocomplete-a11y-evaluate.js +++ b/lib/checks/forms/autocomplete-a11y-evaluate.js @@ -1,5 +1,4 @@ import { isValidAutocomplete } from '../../commons/text'; -import ErrorHandler from '../../core/errors/error-handler'; function checkIsElementValidAutocomplete(node, options, virtualNode) { const autocomplete = virtualNode.attr('autocomplete')?.toLowerCase().trim(); @@ -67,7 +66,10 @@ function autocompleteA11yEvaluate(node, options, virtualNode) { return checkIsElementValidAutocomplete(node, options, virtualNode); } } catch (err) { - ErrorHandler.addCheckError('autocomplete-attribute-valid-check', err); + a11yEngine.errorHandler.addCheckError( + 'autocomplete-attribute-valid-check', + err + ); return undefined; } }