accessibility.js (6159B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 loader.lazyRequireGetter( 8 this, 9 "colorUtils", 10 "resource://devtools/shared/css/color.js", 11 true 12 ); 13 const { 14 accessibility: { 15 SCORES: { FAIL, AA, AAA }, 16 }, 17 } = require("resource://devtools/shared/constants.js"); 18 19 /** 20 * Mapping of text size to contrast ratio score levels 21 */ 22 const LEVELS = { 23 LARGE_TEXT: { AA: 3, AAA: 4.5 }, 24 REGULAR_TEXT: { AA: 4.5, AAA: 7 }, 25 }; 26 27 /** 28 * Mapping of large text size to CSS pixel value 29 */ 30 const LARGE_TEXT = { 31 // CSS pixel value (constant) that corresponds to 14 point text size which defines large 32 // text when font text is bold (font weight is greater than or equal to 600). 33 BOLD_LARGE_TEXT_MIN_PIXELS: 18.66, 34 // CSS pixel value (constant) that corresponds to 18 point text size which defines large 35 // text for normal text (e.g. not bold). 36 LARGE_TEXT_MIN_PIXELS: 24, 37 }; 38 39 /** 40 * Get contrast ratio score based on WCAG criteria. 41 * 42 * @param {number} ratio 43 * Value of the contrast ratio for a given accessible object. 44 * @param {boolean} isLargeText 45 * True if the accessible object contains large text. 46 * @return {string} 47 * Value that represents calculated contrast ratio score. 48 */ 49 function getContrastRatioScore(ratio, isLargeText) { 50 const levels = isLargeText ? LEVELS.LARGE_TEXT : LEVELS.REGULAR_TEXT; 51 52 let score = FAIL; 53 if (ratio >= levels.AAA) { 54 score = AAA; 55 } else if (ratio >= levels.AA) { 56 score = AA; 57 } 58 59 return score; 60 } 61 62 /** 63 * Get calculated text style properties from a node's computed style, if possible. 64 * 65 * @param {object} computedStyle 66 * Computed style using which text styling information is to be calculated. 67 * - fontSize {String} 68 * Font size of the text 69 * - fontWeight {String} 70 * Font weight of the text 71 * - color {String} 72 * Rgb color of the text 73 * - opacity {String} Optional 74 * Opacity of the text 75 * @return {object} 76 * Color and text size information for a given DOM node. 77 */ 78 function getTextProperties(computedStyle) { 79 const { color, fontSize, fontWeight } = computedStyle; 80 let { r, g, b, a } = InspectorUtils.colorToRGBA(color); 81 82 // If the element has opacity in addition to background alpha value, take it 83 // into account. TODO: this does not handle opacity set on ancestor elements 84 // (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1544721). 85 const opacity = computedStyle.opacity 86 ? parseFloat(computedStyle.opacity) 87 : null; 88 if (opacity) { 89 a = opacity * a; 90 } 91 92 const textRgbaColor = new colorUtils.CssColor( 93 `rgba(${r}, ${g}, ${b}, ${a})`, 94 true 95 ); 96 // TODO: For cases where text color is transparent, it likely comes from the color of 97 // the background that is underneath it (commonly from background-clip: text 98 // property). With some additional investigation it might be possible to calculate the 99 // color contrast where the color of the background is used as text color and the 100 // color of the ancestor's background is used as its background. 101 if (textRgbaColor.isTransparent()) { 102 return null; 103 } 104 105 const isBoldText = parseInt(fontWeight, 10) >= 600; 106 const size = parseFloat(fontSize); 107 const isLargeText = 108 size >= 109 (isBoldText 110 ? LARGE_TEXT.BOLD_LARGE_TEXT_MIN_PIXELS 111 : LARGE_TEXT.LARGE_TEXT_MIN_PIXELS); 112 113 return { 114 color: [r, g, b, a], 115 isLargeText, 116 isBoldText, 117 size, 118 opacity, 119 }; 120 } 121 122 /** 123 * Calculates contrast ratio or range of contrast ratios of the referenced DOM node 124 * against the given background color data. If background is multi-colored, return a 125 * range, otherwise a single contrast ratio. 126 * 127 * @param {object} backgroundColorData 128 * Object with one or more of the following properties: 129 * - value {Array} 130 * rgba array for single color background 131 * - min {Array} 132 * min luminance rgba array for multi color background 133 * - max {Array} 134 * max luminance rgba array for multi color background 135 * @param {object} textData 136 * - color {Array} 137 * rgba array for text of referenced DOM node 138 * - isLargeText {Boolean} 139 * True if text of referenced DOM node is large 140 * @return {object} 141 * An object that may contain one or more of the following fields: error, 142 * isLargeText, value, min, max values for contrast. 143 */ 144 function getContrastRatioAgainstBackground( 145 backgroundColorData, 146 { color, isLargeText } 147 ) { 148 if (backgroundColorData.value) { 149 const value = colorUtils.calculateContrastRatio( 150 backgroundColorData.value, 151 color 152 ); 153 return { 154 value, 155 color, 156 backgroundColor: backgroundColorData.value, 157 isLargeText, 158 score: getContrastRatioScore(value, isLargeText), 159 }; 160 } 161 162 let { min: backgroundColorMin, max: backgroundColorMax } = 163 backgroundColorData; 164 let min = colorUtils.calculateContrastRatio(backgroundColorMin, color); 165 let max = colorUtils.calculateContrastRatio(backgroundColorMax, color); 166 167 // Flip minimum and maximum contrast ratios if necessary. 168 if (min > max) { 169 [min, max] = [max, min]; 170 [backgroundColorMin, backgroundColorMax] = [ 171 backgroundColorMax, 172 backgroundColorMin, 173 ]; 174 } 175 176 const score = getContrastRatioScore(min, isLargeText); 177 178 return { 179 min, 180 max, 181 color, 182 backgroundColorMin, 183 backgroundColorMax, 184 isLargeText, 185 score, 186 scoreMin: score, 187 scoreMax: getContrastRatioScore(max, isLargeText), 188 }; 189 } 190 191 exports.getContrastRatioScore = getContrastRatioScore; 192 exports.getTextProperties = getTextProperties; 193 exports.getContrastRatioAgainstBackground = getContrastRatioAgainstBackground; 194 exports.LARGE_TEXT = LARGE_TEXT;