diff options
Diffstat (limited to 'devtools/shared/accessibility.js')
-rw-r--r-- | devtools/shared/accessibility.js | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/devtools/shared/accessibility.js b/devtools/shared/accessibility.js new file mode 100644 index 0000000000..c706054ee3 --- /dev/null +++ b/devtools/shared/accessibility.js @@ -0,0 +1,194 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +loader.lazyRequireGetter( + this, + "colorUtils", + "resource://devtools/shared/css/color.js", + true +); +const { + accessibility: { + SCORES: { FAIL, AA, AAA }, + }, +} = require("resource://devtools/shared/constants.js"); + +/** + * Mapping of text size to contrast ratio score levels + */ +const LEVELS = { + LARGE_TEXT: { AA: 3, AAA: 4.5 }, + REGULAR_TEXT: { AA: 4.5, AAA: 7 }, +}; + +/** + * Mapping of large text size to CSS pixel value + */ +const LARGE_TEXT = { + // CSS pixel value (constant) that corresponds to 14 point text size which defines large + // text when font text is bold (font weight is greater than or equal to 600). + BOLD_LARGE_TEXT_MIN_PIXELS: 18.66, + // CSS pixel value (constant) that corresponds to 18 point text size which defines large + // text for normal text (e.g. not bold). + LARGE_TEXT_MIN_PIXELS: 24, +}; + +/** + * Get contrast ratio score based on WCAG criteria. + * @param {Number} ratio + * Value of the contrast ratio for a given accessible object. + * @param {Boolean} isLargeText + * True if the accessible object contains large text. + * @return {String} + * Value that represents calculated contrast ratio score. + */ +function getContrastRatioScore(ratio, isLargeText) { + const levels = isLargeText ? LEVELS.LARGE_TEXT : LEVELS.REGULAR_TEXT; + + let score = FAIL; + if (ratio >= levels.AAA) { + score = AAA; + } else if (ratio >= levels.AA) { + score = AA; + } + + return score; +} + +/** + * Get calculated text style properties from a node's computed style, if possible. + * @param {Object} computedStyle + * Computed style using which text styling information is to be calculated. + * - fontSize {String} + * Font size of the text + * - fontWeight {String} + * Font weight of the text + * - color {String} + * Rgb color of the text + * - opacity {String} Optional + * Opacity of the text + * @return {Object} + * Color and text size information for a given DOM node. + */ +function getTextProperties(computedStyle) { + const { color, fontSize, fontWeight } = computedStyle; + let { r, g, b, a } = colorUtils.colorToRGBA(color, true); + + // If the element has opacity in addition to background alpha value, take it + // into account. TODO: this does not handle opacity set on ancestor elements + // (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1544721). + const opacity = computedStyle.opacity + ? parseFloat(computedStyle.opacity) + : null; + if (opacity) { + a = opacity * a; + } + + const textRgbaColor = new colorUtils.CssColor( + `rgba(${r}, ${g}, ${b}, ${a})`, + true + ); + // TODO: For cases where text color is transparent, it likely comes from the color of + // the background that is underneath it (commonly from background-clip: text + // property). With some additional investigation it might be possible to calculate the + // color contrast where the color of the background is used as text color and the + // color of the ancestor's background is used as its background. + if (textRgbaColor.isTransparent()) { + return null; + } + + const isBoldText = parseInt(fontWeight, 10) >= 600; + const size = parseFloat(fontSize); + const isLargeText = + size >= + (isBoldText + ? LARGE_TEXT.BOLD_LARGE_TEXT_MIN_PIXELS + : LARGE_TEXT.LARGE_TEXT_MIN_PIXELS); + + return { + color: [r, g, b, a], + isLargeText, + isBoldText, + size, + opacity, + }; +} + +/** + * Calculates contrast ratio or range of contrast ratios of the referenced DOM node + * against the given background color data. If background is multi-colored, return a + * range, otherwise a single contrast ratio. + * + * @param {Object} backgroundColorData + * Object with one or more of the following properties: + * - value {Array} + * rgba array for single color background + * - min {Array} + * min luminance rgba array for multi color background + * - max {Array} + * max luminance rgba array for multi color background + * @param {Object} textData + * - color {Array} + * rgba array for text of referenced DOM node + * - isLargeText {Boolean} + * True if text of referenced DOM node is large + * @return {Object} + * An object that may contain one or more of the following fields: error, + * isLargeText, value, min, max values for contrast. + */ +function getContrastRatioAgainstBackground( + backgroundColorData, + { color, isLargeText } +) { + if (backgroundColorData.value) { + const value = colorUtils.calculateContrastRatio( + backgroundColorData.value, + color + ); + return { + value, + color, + backgroundColor: backgroundColorData.value, + isLargeText, + score: getContrastRatioScore(value, isLargeText), + }; + } + + let { + min: backgroundColorMin, + max: backgroundColorMax, + } = backgroundColorData; + let min = colorUtils.calculateContrastRatio(backgroundColorMin, color); + let max = colorUtils.calculateContrastRatio(backgroundColorMax, color); + + // Flip minimum and maximum contrast ratios if necessary. + if (min > max) { + [min, max] = [max, min]; + [backgroundColorMin, backgroundColorMax] = [ + backgroundColorMax, + backgroundColorMin, + ]; + } + + const score = getContrastRatioScore(min, isLargeText); + + return { + min, + max, + color, + backgroundColorMin, + backgroundColorMax, + isLargeText, + score, + scoreMin: score, + scoreMax: getContrastRatioScore(max, isLargeText), + }; +} + +exports.getContrastRatioScore = getContrastRatioScore; +exports.getTextProperties = getTextProperties; +exports.getContrastRatioAgainstBackground = getContrastRatioAgainstBackground; +exports.LARGE_TEXT = LARGE_TEXT; |