Bug 1513557 - Adding overlapping swatch preview for colour contrast indicator in a11y panel. r=pbro,flod, a=RyanVM, l10n=flod
authorYura Zenevich <yura.zenevich@gmail.com>
Sun, 16 Dec 2018 13:49:51 +0000
changeset 509048 627384d9dfe3a620a8632a30bec4f1a8ab04722f
parent 509047 6ee21f3f0592b2522ade70a367b47b7fab5eaa43
child 509049 dfe78db5e4ae6de6052b22e0e2d7ced375a0110c
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro, flod, RyanVM
bugs1513557
milestone65.0
Bug 1513557 - Adding overlapping swatch preview for colour contrast indicator in a11y panel. r=pbro,flod, a=RyanVM, l10n=flod MozReview-Commit-ID: 9IKGXorA6uS Differential Revision: https://phabricator.services.mozilla.com/D14429
devtools/server/actors/highlighters.css
devtools/server/actors/highlighters/utils/accessibility.js
devtools/server/actors/highlighters/xul-accessible.js
devtools/server/actors/utils/accessibility.js
devtools/shared/css/color.js
devtools/shared/locales/en-US/accessibility.properties
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -32,16 +32,18 @@
   --highlighter-bubble-text-color: hsl(216, 33%, 97%);
   --highlighter-bubble-background-color: hsl(214, 13%, 24%);
   --highlighter-bubble-border-color: rgba(255, 255, 255, 0.2);
   --highlighter-bubble-arrow-size: 8px;
   --highlighter-font-family: message-box;
   --highlighter-font-size: 11px;
   --highlighter-infobar-color: hsl(210, 30%, 85%);
   --highlighter-marker-color: #000;
+
+  --grey-40: #b1b1b3;
 }
 
 /**
  * Highlighters are asbolute positioned in the page by default.
  * A single highlighter can have fixed position in its css class if needed (see below the
  * eye dropper or rulers highlighter, for example); but if it has to handle the
  * document's scrolling (as rulers does), it would lag a bit behind due the APZ (Async
  * Pan/Zoom module), that performs asynchronously panning and zooming on the compositor
@@ -648,16 +650,27 @@
 }
 
 :-moz-native-anonymous .accessible-infobar-name,
 :-moz-native-anonymous .accessible-infobar-audit {
   color: var(--highlighter-infobar-color);
   max-width: 90%;
 }
 
+:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):before {
+  content: "";
+  height: 8px;
+  width: 8px;
+  display: inline-flex;
+  background-color: var(--accessibility-highlighter-contrast-ratio-color);
+  box-shadow: 0 0 0 1px var(--grey-40), 4px 3px var(--accessibility-highlighter-contrast-ratio-bg), 4px 3px 0 1px var(--grey-40);
+  margin-inline-start: 3px;
+  margin-inline-end: 9px;
+}
+
 :-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):after {
   margin-inline-start: 2px;
 }
 
 :-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA:after,
 :-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after {
   color: #90E274;
 }
@@ -671,28 +684,25 @@
   content: "AA\2713";
 }
 
 :-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after {
   content: "AAA\2713";
 }
 
 :-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-label,
-:-moz-native-anonymous .accessible-infobar-audit #accessible-contrast-ratio-max:not(:empty):before {
+:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-separator:before {
   margin-inline-end: 3px;
 }
 
-:-moz-native-anonymous .accessible-infobar-audit #accessible-contrast-ratio-max {
+:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-separator:before {
+  content: "-";
   margin-inline-start: 3px;
 }
 
-:-moz-native-anonymous .accessible-infobar-audit #accessible-contrast-ratio-max:not(:empty):before {
-  content: "-";
-}
-
 :-moz-native-anonymous .accessible-infobar-name:not(:empty),
 :-moz-native-anonymous .accessible-infobar-audit:not(:empty) {
   border-inline-start: 1px solid #5a6169;
   margin-inline-start: 6px;
   padding-inline-start: 6px;
 }
 
 :-moz-native-anonymous .accessible-infobar-role {
--- a/devtools/server/actors/highlighters/utils/accessibility.js
+++ b/devtools/server/actors/highlighters/utils/accessibility.js
@@ -474,24 +474,23 @@ class ContrastRatio extends AuditReport 
     createNode(this.win, {
       nodeType: "span",
       parent: root,
       attributes: {
         "class": "contrast-ratio-label",
         "id": "contrast-ratio-label",
       },
       prefix: this.prefix,
-      text: L10N.getStr("accessibility.contrast.ratio.label"),
     });
 
     createNode(this.win, {
       nodeType: "span",
       parent: root,
       attributes: {
-        "class": "contrast-ratio",
+        "class": "contrast-ratio-error",
         "id": "contrast-ratio-error",
       },
       prefix: this.prefix,
       text: L10N.getStr("accessibility.contrast.ratio.error"),
     });
 
     createNode(this.win, {
       nodeType: "span",
@@ -502,69 +501,91 @@ class ContrastRatio extends AuditReport 
       },
       prefix: this.prefix,
     });
 
     createNode(this.win, {
       nodeType: "span",
       parent: root,
       attributes: {
+        "class": "contrast-ratio-separator",
+        "id": "contrast-ratio-separator",
+      },
+      prefix: this.prefix,
+    });
+
+    createNode(this.win, {
+      nodeType: "span",
+      parent: root,
+      attributes: {
         "class": "contrast-ratio",
         "id": "contrast-ratio-max",
       },
       prefix: this.prefix,
     });
   }
 
-  _fillAndStyleContrastValue(el, value, isLargeText, stringName) {
+  _fillAndStyleContrastValue(el, { value, isLargeText, color, backgroundColor }) {
     value = value.toFixed(2);
     const style = getContrastRatioScoreStyle(value, isLargeText);
-    this.setTextContent(el, stringName ? L10N.getFormatStr(stringName, value) : value);
+    this.setTextContent(el, value);
     el.classList.add(style);
+    el.setAttribute("style",
+      `--accessibility-highlighter-contrast-ratio-color: rgba(${color});` +
+      `--accessibility-highlighter-contrast-ratio-bg: rgba(${backgroundColor});`);
     el.removeAttribute("hidden");
   }
 
   /**
    * Update contrast ratio score infobar markup.
    * @param  {Number}
    *         Contrast ratio for an accessible object being highlighted.
    * @return {Boolean}
    *         True if the contrast ratio markup was updated correctly and infobar audit
    *         block should be visible.
    */
   update({ contrastRatio }) {
     const els = {};
-    for (const key of ["label", "min", "max", "error"]) {
+    for (const key of ["label", "min", "max", "error", "separator"]) {
       const el = els[key] = this.getElement(`contrast-ratio-${key}`);
       if (["min", "max"].includes(key)) {
         ["fail", "AA", "AAA"].forEach(className => el.classList.remove(className));
         this.setTextContent(el, "");
       }
 
       el.setAttribute("hidden", true);
+      el.removeAttribute("style");
     }
 
     if (!contrastRatio) {
       return false;
     }
 
     const { isLargeText, error } = contrastRatio;
+    this.setTextContent(els.label,
+      L10N.getStr(`accessibility.contrast.ratio.label${isLargeText ? ".large" : ""}`));
     els.label.removeAttribute("hidden");
     if (error) {
       els.error.removeAttribute("hidden");
       return true;
     }
 
     if (contrastRatio.value) {
-      this._fillAndStyleContrastValue(els.min, contrastRatio.value, isLargeText);
+      const { value, color, backgroundColor } = contrastRatio;
+      this._fillAndStyleContrastValue(els.min,
+        { value, isLargeText, color, backgroundColor });
       return true;
     }
 
-    this._fillAndStyleContrastValue(els.min, contrastRatio.min, isLargeText);
-    this._fillAndStyleContrastValue(els.max, contrastRatio.max, isLargeText);
+    const { min, max, color, backgroundColorMin, backgroundColorMax } = contrastRatio;
+    this._fillAndStyleContrastValue(els.min,
+      { value: min, isLargeText, color, backgroundColor: backgroundColorMin });
+    els.separator.removeAttribute("hidden");
+    this._fillAndStyleContrastValue(els.max,
+      { value: max, isLargeText, color, backgroundColor: backgroundColorMax });
 
     return true;
   }
 }
 
 /**
  * A helper function that calculate accessible object bounds and positioning to
  * be used for highlighting.
--- a/devtools/server/actors/highlighters/xul-accessible.js
+++ b/devtools/server/actors/highlighters/xul-accessible.js
@@ -13,16 +13,18 @@ const { TEXT_NODE } = require("devtools/
  * Stylesheet used for highlighter styling of accessible objects in chrome. It
  * is consistent with the styling of an in-content accessible highlighter.
  */
 const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURIComponent(`
   .highlighter-container {
     --highlighter-bubble-background-color: hsl(214, 13%, 24%);
     --highlighter-bubble-border-color: rgba(255, 255, 255, 0.2);
     --highlighter-bubble-arrow-size: 8px;
+
+    --grey-40: #b1b1b3;
   }
 
   .accessible-bounds {
     position: fixed;
     pointer-events: none;
     z-index: 10;
     display: block;
     background-color: #6a5acd!important;
@@ -74,16 +76,29 @@ const ACCESSIBLE_BOUNDS_SHEET = "data:te
   }
 
   .accessible-infobar-name,
   .accessible-infobar-audit {
     color: hsl(210, 30%, 85%);
     max-width: 90%;
   }
 
+  .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):before {
+    content: "";
+    height: 8px;
+    width: 8px;
+    display: inline-flex;
+    background-color: var(--accessibility-highlighter-contrast-ratio-color);
+    box-shadow: 0 0 0 1px var(--grey-40),
+                4px 3px var(--accessibility-highlighter-contrast-ratio-bg),
+                4px 3px 0 1px var(--grey-40);
+    margin-inline-start: 3px;
+    margin-inline-end: 9px;
+  }
+
   .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):after {
     margin-inline-start: 2px;
   }
 
   .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA:after,
   .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after {
     color: #90E274;
   }
@@ -97,28 +112,25 @@ const ACCESSIBLE_BOUNDS_SHEET = "data:te
     content: "AA\u2713";
   }
 
   .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after {
     content: "AAA\u2713";
   }
 
   .accessible-infobar-audit .accessible-contrast-ratio-label,
-  .accessible-infobar-audit #accessible-contrast-ratio-max:not(:empty):before {
+  .accessible-infobar-audit .accessible-contrast-ratio-separator:before {
     margin-inline-end: 3px;
   }
 
-  .accessible-infobar-audit #accessible-contrast-ratio-max {
+  .accessible-infobar-audit .accessible-contrast-ratio-separator:before {
+    content: "-";
     margin-inline-start: 3px;
   }
 
-  .accessible-infobar-audit #accessible-contrast-ratio-max:not(:empty):before {
-    content: "-";
-  }
-
   .accessible-infobar-name:not(:empty),
   .accessible-infobar-audit:not(:empty) {
     border-inline-start: 1px solid #5a6169;
     margin-inline-start: 6px;
     padding-inline-start: 6px;
   }
 
   .accessible-infobar-role {
--- a/devtools/server/actors/utils/accessibility.js
+++ b/devtools/server/actors/utils/accessibility.js
@@ -191,28 +191,37 @@ function getContrastRatioFor(node, optio
       error: true,
     };
   }
 
   const { color, isLargeText } = props;
   if (rgba.value) {
     return {
       value: colorUtils.calculateContrastRatio(rgba.value, color),
+      color,
+      backgroundColor: rgba.value,
       isLargeText,
     };
   }
 
-  // calculateContrastRatio modifies the array, since we need to use color array twice,
-  // pass its copy to the method.
-  const min = colorUtils.calculateContrastRatio(rgba.min, Array.from(color));
-  const max = colorUtils.calculateContrastRatio(rgba.max, Array.from(color));
+  let min = colorUtils.calculateContrastRatio(rgba.min, color);
+  let max = colorUtils.calculateContrastRatio(rgba.max, color);
+
+  // Flip minimum and maximum contrast ratios if necessary.
+  if (min > max) {
+    [min, max] = [max, min];
+    [rgba.min, rgba.max] = [rgba.max, rgba.min];
+  }
 
   return {
-    min: min < max ? min : max,
-    max: min < max ? max : min,
+    min,
+    max,
+    color,
+    backgroundColorMin: rgba.min,
+    backgroundColorMax: rgba.max,
     isLargeText,
   };
 }
 
 /**
  * Helper function that determines if nsIAccessible object is in defunct state.
  *
  * @param  {nsIAccessible}  accessible
--- a/devtools/shared/css/color.js
+++ b/devtools/shared/css/color.js
@@ -1192,16 +1192,20 @@ function blendColors(foregroundColor, ba
  *
  * @param {Array} backgroundColor An array with [r,g,b,a] values containing
  * the background color.
  * @param {Array} textColor An array with [r,g,b,a] values containing
  * the text color.
  * @return {Number} The calculated luminance.
  */
 function calculateContrastRatio(backgroundColor, textColor) {
+  // Do not modify given colors.
+  backgroundColor = Array.from(backgroundColor);
+  textColor = Array.from(textColor);
+
   backgroundColor = blendColors(backgroundColor);
   textColor = blendColors(textColor, backgroundColor);
 
   const backgroundLuminance = calculateLuminance(backgroundColor);
   const textLuminance = calculateLuminance(textColor);
   const ratio = (textLuminance + 0.05) / (backgroundLuminance + 0.05);
 
   return (ratio > 1.0) ? ratio : (1 / ratio);
--- a/devtools/shared/locales/en-US/accessibility.properties
+++ b/devtools/shared/locales/en-US/accessibility.properties
@@ -9,8 +9,13 @@ accessibility.contrast.ratio=Contrast: %
 
 # LOCALIZATION NOTE (accessibility.contrast.ratio.error): A title text for the color
 # contrast ratio, used when the tool is unable to calculate the contrast ratio value.
 accessibility.contrast.ratio.error=Unable to calculate
 
 # LOCALIZATION NOTE (accessibility.contrast.ratio.label): A title text for the color
 # contrast ratio description, used together with the actual values.
 accessibility.contrast.ratio.label=Contrast:
+
+# LOCALIZATION NOTE (accessibility.contrast.ratio.label.large): A title text for the color
+# contrast ratio description that also specifies that the color contrast criteria used is
+# if for large text.
+accessibility.contrast.ratio.label.large=Contrast (large text):