summaryrefslogtreecommitdiffstats
path: root/devtools/shared/accessibility.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/shared/accessibility.js192
1 files changed, 192 insertions, 0 deletions
diff --git a/devtools/shared/accessibility.js b/devtools/shared/accessibility.js
new file mode 100644
index 0000000000..db6983d033
--- /dev/null
+++ b/devtools/shared/accessibility.js
@@ -0,0 +1,192 @@
+/* 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 } = InspectorUtils.colorToRGBA(color);
+
+ // 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;