Bug 1549626 - fix an issue with incorrect text colour blending. Fallback on basic colour contrast algorythm when canvas approach fails. r=mtigley
authorYura Zenevich <yura.zenevich@gmail.com>
Wed, 08 May 2019 15:04:57 +0000
changeset 531890 3bf1f379d77e758092f84df765e8c8726b0f3b98
parent 531889 616fc2abbbec6c4e8d0bcf00ee682745c24674bb
child 531891 7fe9661e8510f9bf0430573aaceb7ed478e472b2
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmtigley
bugs1549626
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1549626 - fix an issue with incorrect text colour blending. Fallback on basic colour contrast algorythm when canvas approach fails. r=mtigley Differential Revision: https://phabricator.services.mozilla.com/D30145
devtools/server/actors/accessibility/contrast.js
--- a/devtools/server/actors/accessibility/contrast.js
+++ b/devtools/server/actors/accessibility/contrast.js
@@ -7,16 +7,17 @@
 loader.lazyRequireGetter(this, "colorUtils", "devtools/shared/css/color", true);
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/actors/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "getBounds", "devtools/server/actors/highlighters/utils/accessibility", true);
 loader.lazyRequireGetter(this, "getCurrentZoom", "devtools/shared/layout/utils", true);
 loader.lazyRequireGetter(this, "addPseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true);
 loader.lazyRequireGetter(this, "removePseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true);
 loader.lazyRequireGetter(this, "DevToolsWorker", "devtools/shared/worker/worker", true);
 loader.lazyRequireGetter(this, "accessibility", "devtools/shared/constants", true);
+loader.lazyRequireGetter(this, "InspectorActorUtils", "devtools/server/actors/inspector/utils");
 
 const WORKER_URL = "resource://devtools/server/actors/accessibility/worker.js";
 const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted";
 // 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).
 const 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).
@@ -36,16 +37,19 @@ function getTextProperties(node) {
   if (!computedStyles) {
     return null;
   }
 
   const { color, "font-size": fontSize, "font-weight": fontWeight } = computedStyles;
   const opacity = parseFloat(computedStyles.opacity);
 
   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).
   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()) {
@@ -53,21 +57,21 @@ function getTextProperties(node) {
   }
 
   const isBoldText = parseInt(fontWeight, 10) >= 600;
   const size = parseFloat(fontSize);
   const isLargeText =
     size >= (isBoldText ? BOLD_LARGE_TEXT_MIN_PIXELS : LARGE_TEXT_MIN_PIXELS);
 
   return {
-    // Blend text color taking its alpha into account asuming white background.
-    color: colorUtils.blendColors([r, g, b, a]),
+    color: [r, g, b, a],
     isLargeText,
     isBoldText,
     size,
+    opacity,
   };
 }
 
 /**
  * Get canvas rendering context for the current target window bound by the bounds of the
  * accessible objects.
  * @param  {Object}  win
  *         Current target window.
@@ -151,17 +155,17 @@ function getContrastRatioScore(ratio, is
 async function getContrastRatioFor(node, options = {}) {
   const props = getTextProperties(node);
   if (!props) {
     return {
       error: true,
     };
   }
 
-  const { color, isLargeText, isBoldText, size } = props;
+  const { color, isLargeText, isBoldText, size, opacity } = props;
   const bounds = getBounds(options.win, options.bounds);
   const zoom = 1 / getCurrentZoom(options.win);
   // When calculating colour contrast, we traverse image data for text nodes that are
   // drawn both with and without transparent text. Image data arrays are typically really
   // big. In cases when the font size is fairly large or when the page is zoomed in image
   // data is especially large (retrieving it and/or traversing it takes significant amount
   // of time). Here we optimize the size of the image data by scaling down the drawn nodes
   // to a size where their text size equals either BOLD_LARGE_TEXT_MIN_PIXELS or
@@ -190,18 +194,43 @@ async function getContrastRatioFor(node,
     0, 0, bounds.width * scale, bounds.height * scale);
 
   const rgba = await worker.performTask("getBgRGBA", {
     dataTextBuf: dataText.buffer,
     dataBackgroundBuf: dataBackground.buffer,
   }, [ dataText.buffer, dataBackground.buffer ]);
 
   if (!rgba) {
+    // Fallback (original) contrast calculation algorithm. It tries to get the
+    // closest background colour for the node and use it to calculate contrast.
+    const backgroundColor = InspectorActorUtils.getClosestBackgroundColor(node);
+    const backgroundImage = InspectorActorUtils.getClosestBackgroundImage(node);
+
+    if (backgroundImage !== "none") {
+      // Both approaches failed, at this point we don't have a better one yet.
+      return {
+        error: true,
+      };
+    }
+
+    let { r, g, b, a } = colorUtils.colorToRGBA(backgroundColor, 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).
+    if (opacity < 1) {
+      a = opacity * a;
+    }
+
+    const value = colorUtils.calculateContrastRatio([r, g, b, a], color);
     return {
-      error: true,
+      value,
+      color,
+      backgroundColor: [r, g, b, a],
+      isLargeText,
+      score: getContrastRatioScore(value, isLargeText),
     };
   }
 
   if (rgba.value) {
     const value = colorUtils.calculateContrastRatio(rgba.value, color);
     return {
       value,
       color,