Bug 1478152 - Style current color picker to match new design. r?gl draft
authorMicah Tigley <mtigley@mozilla.com>
Thu, 09 Aug 2018 14:04:22 -0400
changeset 830005 4f44be630878844584941c0a3d1d96971ac829e1
parent 830004 9b5aefe66457f3bbb6403dbe7c149993c7c7eb70
push id118807
push userbmo:mtigley@mozilla.com
push dateSat, 18 Aug 2018 03:08:50 +0000
reviewersgl
bugs1478152
milestone63.0a1
Bug 1478152 - Style current color picker to match new design. r?gl MozReview-Commit-ID: GD1QPqPmFMk
devtools/client/shared/test/browser_spectrum.js
devtools/client/shared/widgets/Spectrum.js
devtools/client/shared/widgets/spectrum.css
devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
devtools/client/themes/variables.css
devtools/shared/css/color.js
--- a/devtools/client/shared/test/browser_spectrum.js
+++ b/devtools/client/shared/test/browser_spectrum.js
@@ -15,20 +15,76 @@ add_task(async function() {
 
   const container = doc.getElementById("spectrum-container");
 
   await testCreateAndDestroyShouldAppendAndRemoveElements(container);
   await testPassingAColorAtInitShouldSetThatColor(container);
   await testSettingAndGettingANewColor(container);
   await testChangingColorShouldEmitEvents(container);
   await testSettingColorShoudUpdateTheUI(container);
+  await testChangingColorShouldUpdateColorPreview(container);
 
   host.destroy();
 });
 
+/**
+ * Helper method for extracting the rgba overlay value of the color preview's background
+ * image style.
+ *
+ * @param   {String} linearGradientStr
+ *          The linear gradient CSS string.
+ * @return  {String} Returns the rgba string for the color overlay.
+ */
+function extractRgbaOverlayString(linearGradientStr) {
+  const start = linearGradientStr.indexOf("(");
+  const end = linearGradientStr.indexOf(")");
+
+  return linearGradientStr.substring(start + 1, end + 1);
+}
+
+/**
+ * Helper method for retrieving an element's specified CSS property.
+ *
+ * @param   {Element} element
+ *          The element to get CSS property value from.
+ * @param   {String} property
+ *          Specified CSS property.
+ * @return  {String} the CSS property value of specified element.
+ */
+function getComputedStyle(element, property) {
+  return window.getComputedStyle(element).getPropertyValue(property).trim();
+}
+
+/**
+ * Helper method for testing color preview display.
+ *
+ * @param   {Object} spectrum
+ *          Color picker widget.
+ * @param   {String} expectedRgbCssString
+ *          The expected rgb string overlay for the color preview.
+ * @param   {String} expectedBorderColor
+ *          The expected border color for the color preview.
+ */
+function testColorPreviewDisplay(spectrum, expectedRgbCssString, expectedBorderColor) {
+  const { colorPreview } = spectrum;
+
+  spectrum.updateUI();
+
+  // Extract the first rgba value from the linear gradient
+  const linearGradientStr = getComputedStyle(colorPreview, "background-image");
+  const colorPreviewValue = extractRgbaOverlayString(linearGradientStr);
+
+  is(colorPreviewValue, expectedRgbCssString,
+    `Color preview should be ${expectedRgbCssString}`);
+
+  // Test if color preview's border color is correct.
+  const borderColor = getComputedStyle(colorPreview, "--preview-border-color");
+  is(borderColor, expectedBorderColor, "Color preview border color is correct.");
+}
+
 function testCreateAndDestroyShouldAppendAndRemoveElements(container) {
   ok(container, "We have the root node to append spectrum to");
   is(container.childElementCount, 0, "Root node is empty");
 
   const s = new Spectrum(container, [255, 126, 255, 1]);
   s.show();
   ok(container.childElementCount > 0, "Spectrum has appended elements");
 
@@ -90,23 +146,51 @@ function testChangingColorShouldEmitEven
   });
 }
 
 function testSettingColorShoudUpdateTheUI(container) {
   const s = new Spectrum(container, [255, 255, 255, 1]);
   s.show();
   const dragHelperOriginalPos = [s.dragHelper.style.top, s.dragHelper.style.left];
   const alphaHelperOriginalPos = s.alphaSliderHelper.style.left;
+  let hueHelperOriginalPos = s.hueSliderHelper.style.left;
 
   s.rgb = [50, 240, 234, .2];
   s.updateUI();
 
   ok(s.alphaSliderHelper.style.left != alphaHelperOriginalPos, "Alpha helper has moved");
   ok(s.dragHelper.style.top !== dragHelperOriginalPos[0], "Drag helper has moved");
   ok(s.dragHelper.style.left !== dragHelperOriginalPos[1], "Drag helper has moved");
+  ok(s.hueSliderHelper.style.left !== hueHelperOriginalPos, "Hue helper has moved");
+
+  hueHelperOriginalPos = s.hueSliderHelper.style.left;
 
   s.rgb = [240, 32, 124, 0];
   s.updateUI();
   is(s.alphaSliderHelper.style.left, -(s.alphaSliderHelper.offsetWidth / 2) + "px",
     "Alpha range UI has been updated again");
+  ok(hueHelperOriginalPos !== s.hueSliderHelper.style.left,
+    "Hue Helper slider should have move again");
 
   s.destroy();
 }
+
+function testChangingColorShouldUpdateColorPreview(container) {
+  const s = new Spectrum(container, [0, 0, 1, 1]);
+  s.show();
+
+  info("Test that color preview is black.");
+  testColorPreviewDisplay(s, "rgb(0, 0, 1)", "transparent");
+
+  info("Test that color preview is blue.");
+  s.rgb = [0, 0, 255, 1];
+  testColorPreviewDisplay(s, "rgb(0, 0, 255)", "transparent");
+
+  info("Test that color preview is red.");
+  s.rgb = [255, 0, 0, 1];
+  testColorPreviewDisplay(s, "rgb(255, 0, 0)", "transparent");
+
+  info("Test that color preview is white and also has a light grey border.");
+  s.rgb = [255, 255, 255, 1];
+  testColorPreviewDisplay(s, "rgb(255, 255, 255)", "rgb(204, 204, 204)");
+
+  s.destroy();
+}
--- a/devtools/client/shared/widgets/Spectrum.js
+++ b/devtools/client/shared/widgets/Spectrum.js
@@ -1,15 +1,16 @@
 /* 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";
 
 const EventEmitter = require("devtools/shared/event-emitter");
+const { colorUtils } = require("devtools/shared/css/color.js");
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Spectrum creates a color picker widget in any container you give it.
  *
  * Simple usage example:
  *
  * const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
@@ -28,56 +29,76 @@ const XHTML_NS = "http://www.w3.org/1999
  * visible will allow spectrum to correctly initialize its various parts.
  *
  * Fires the following events:
  * - changed : When the user changes the current color
  */
 function Spectrum(parentEl, rgb) {
   EventEmitter.decorate(this);
 
+  this.document = parentEl.ownerDocument;
   this.element = parentEl.ownerDocument.createElementNS(XHTML_NS, "div");
   this.parentEl = parentEl;
 
   this.element.className = "spectrum-container";
   this.element.innerHTML = `
-    <div class="spectrum-top">
-      <div class="spectrum-fill"></div>
-      <div class="spectrum-top-inner">
-        <div class="spectrum-color spectrum-box">
-          <div class="spectrum-sat">
-            <div class="spectrum-val">
-              <div class="spectrum-dragger"></div>
-            </div>
+    <section class="spectrum-color-picker">
+      <div class="spectrum-color spectrum-box">
+        <div class="spectrum-sat">
+          <div class="spectrum-val">
+            <div class="spectrum-dragger"></div>
           </div>
         </div>
+      </div>
+    </section>
+    <section class="spectrum-controls">
+      <div class="spectrum-color-preview"></div>
+      <div class="spectrum-slider-container">
         <div class="spectrum-hue spectrum-box">
-          <div class="spectrum-slider spectrum-slider-control"></div>
+          <div class="spectrum-hue-inner">
+            <div class="spectrum-hue-handle spectrum-slider-control"></div>
+          </div>
         </div>
-      </div>
-    </div>
-    <div class="spectrum-alpha spectrum-checker spectrum-box">
-      <div class="spectrum-alpha-inner">
-        <div class="spectrum-alpha-handle spectrum-slider-control"></div>
-      </div>
-    </div>
+        <div class="spectrum-alpha spectrum-checker spectrum-box">
+          <div class="spectrum-alpha-inner">
+            <div class="spectrum-alpha-handle spectrum-slider-control"></div>
+          </div>
+         </div>
+        </div>
+     </section>
   `;
 
   this.onElementClick = this.onElementClick.bind(this);
   this.element.addEventListener("click", this.onElementClick);
 
   this.parentEl.appendChild(this.element);
 
-  this.slider = this.element.querySelector(".spectrum-hue");
-  this.slideHelper = this.element.querySelector(".spectrum-slider");
-  Spectrum.draggable(this.slider, this.onSliderMove.bind(this));
-
+  // Color spectrum dragger.
   this.dragger = this.element.querySelector(".spectrum-color");
   this.dragHelper = this.element.querySelector(".spectrum-dragger");
   Spectrum.draggable(this.dragger, this.onDraggerMove.bind(this));
 
+  // Here we define the components for the "controls" section of the color picker.
+  this.controls = this.element.querySelector(".spectrum-controls");
+  this.colorPreview = this.element.querySelector(".spectrum-color-preview");
+
+  // Create the eyedropper.
+  const eyedropper = this.document.createElementNS(XHTML_NS, "button");
+  eyedropper.id = "eyedropper-button";
+  eyedropper.className = "devtools-button";
+  eyedropper.style.pointerEvents = "auto";
+  this.controls.insertBefore(eyedropper, this.colorPreview);
+
+  // Hue slider
+  this.hueSlider = this.element.querySelector(".spectrum-hue");
+  this.hueSliderInner = this.element.querySelector(".spectrum-hue-inner");
+  this.hueSliderHelper = this.element.querySelector(".spectrum-hue-handle");
+  Spectrum.draggable(this.hueSliderInner, this.onHueSliderMove.bind(this));
+
+  // Alpha slider
   this.alphaSlider = this.element.querySelector(".spectrum-alpha");
   this.alphaSliderInner = this.element.querySelector(".spectrum-alpha-inner");
   this.alphaSliderHelper = this.element.querySelector(".spectrum-alpha-handle");
   Spectrum.draggable(this.alphaSliderInner, this.onAlphaSliderMove.bind(this));
 
   if (rgb) {
     this.rgb = rgb;
     this.updateUI();
@@ -223,35 +244,35 @@ Spectrum.prototype = {
 
   get rgbCssString() {
     const rgb = this.rgb;
     return "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " +
       rgb[3] + ")";
   },
 
   show: function() {
-    this.element.classList.add("spectrum-show");
-
-    this.slideHeight = this.slider.offsetHeight;
     this.dragWidth = this.dragger.offsetWidth;
     this.dragHeight = this.dragger.offsetHeight;
     this.dragHelperHeight = this.dragHelper.offsetHeight;
-    this.slideHelperHeight = this.slideHelper.offsetHeight;
+
     this.alphaSliderWidth = this.alphaSliderInner.offsetWidth;
     this.alphaSliderHelperWidth = this.alphaSliderHelper.offsetWidth;
 
+    this.hueSliderWidth = this.hueSliderInner.offsetWidth;
+    this.hueSliderHelperWidth = this.hueSliderHelper.offsetWidth;
+
     this.updateUI();
   },
 
   onElementClick: function(e) {
     e.stopPropagation();
   },
 
-  onSliderMove: function(dragX, dragY) {
-    this.hsv[0] = (dragY / this.slideHeight);
+  onHueSliderMove: function(dragX, dragY) {
+    this.hsv[0] = (dragX / this.hueSliderWidth);
     this.updateUI();
     this.onChange();
   },
 
   onDraggerMove: function(dragX, dragY) {
     this.hsv[1] = dragX / this.dragWidth;
     this.hsv[2] = (this.dragHeight - dragY) / this.dragHeight;
     this.updateUI();
@@ -263,23 +284,52 @@ Spectrum.prototype = {
     this.updateUI();
     this.onChange();
   },
 
   onChange: function() {
     this.emit("changed", this.rgb, this.rgbCssString);
   },
 
+  updateAlphaSliderBackground: function() {
+    const rgb = this.rgb;
+
+    const rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+    const rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
+    const alphaGradient = "linear-gradient(to right, " + rgbAlpha0 + ", " +
+      rgbNoAlpha + ")";
+    this.alphaSliderInner.style.background = alphaGradient;
+  },
+
+  updateColorPreview: function() {
+    // Overlay the rgba color over a checkered image background.
+    this.colorPreview.style.setProperty("--overlay-color", this.rgbCssString);
+
+    // We should be able to distinguish the color preview on high luminance rgba values.
+    // Give the color preview a light grey border if the luminance of the current rgba
+    // tuple is great.
+    const colorLuminance = colorUtils.calculateLuminance(this.rgb);
+
+    if (colorLuminance > 0.80) {
+      this.colorPreview.style.setProperty(
+        "--preview-border-color", "var(--checkered-background)");
+    } else {
+      this.colorPreview.style.setProperty(
+        "--preview-border-color", "transparent");
+    }
+  },
+
+  updateDraggerBackgroundColor: function() {
+    const flatColor = "rgb(" + this.rgbNoSatVal[0] + ", " + this.rgbNoSatVal[1] + ", " +
+     this.rgbNoSatVal[2] + ")";
+
+    this.dragger.style.backgroundColor = flatColor;
+  },
+
   updateHelperLocations: function() {
-    // If the UI hasn't been shown yet then none of the dimensions will be
-    // correct
-    if (!this.element.classList.contains("spectrum-show")) {
-      return;
-    }
-
     const h = this.hsv[0];
     const s = this.hsv[1];
     const v = this.hsv[2];
 
     // Placing the color dragger
     let dragX = s * this.dragWidth;
     let dragY = this.dragHeight - (v * this.dragHeight);
     const helperDim = this.dragHelperHeight / 2;
@@ -292,47 +342,36 @@ Spectrum.prototype = {
       -helperDim,
       Math.min(this.dragHeight - helperDim, dragY - helperDim)
     );
 
     this.dragHelper.style.top = dragY + "px";
     this.dragHelper.style.left = dragX + "px";
 
     // Placing the hue slider
-    const slideY = (h * this.slideHeight) - this.slideHelperHeight / 2;
-    this.slideHelper.style.top = slideY + "px";
+    const hueSliderX = (h * this.hueSliderWidth) - (this.hueSliderHelperWidth / 2);
+    this.hueSliderHelper.style.left = hueSliderX + "px";
 
     // Placing the alpha slider
     const alphaSliderX = (this.hsv[3] * this.alphaSliderWidth) -
       (this.alphaSliderHelperWidth / 2);
     this.alphaSliderHelper.style.left = alphaSliderX + "px";
   },
 
   updateUI: function() {
     this.updateHelperLocations();
-
-    const rgb = this.rgb;
-    const rgbNoSatVal = this.rgbNoSatVal;
-
-    const flatColor = "rgb(" + rgbNoSatVal[0] + ", " + rgbNoSatVal[1] + ", " +
-      rgbNoSatVal[2] + ")";
-
-    this.dragger.style.backgroundColor = flatColor;
-
-    const rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
-    const rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
-    const alphaGradient = "linear-gradient(to right, " + rgbAlpha0 + ", " +
-      rgbNoAlpha + ")";
-    this.alphaSliderInner.style.background = alphaGradient;
+    this.updateColorPreview();
+    this.updateDraggerBackgroundColor();
+    this.updateAlphaSliderBackground();
   },
 
   destroy: function() {
     this.element.removeEventListener("click", this.onElementClick);
 
     this.parentEl.removeChild(this.element);
 
-    this.slider = null;
+    this.alphaSlider = this.alphaSliderInner = this.alphaSliderHelper = null;
+    this.colorPreview = null;
     this.dragger = null;
-    this.alphaSlider = this.alphaSliderInner = this.alphaSliderHelper = null;
+    this.element = null;
     this.parentEl = null;
-    this.element = null;
   }
 };
--- a/devtools/client/shared/widgets/spectrum.css
+++ b/devtools/client/shared/widgets/spectrum.css
@@ -1,14 +1,14 @@
 /* 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/. */
 
 #eyedropper-button {
-  margin-inline-start: 5px;
+  margin-inline-end: 5px;
   display: block;
 }
 
 #eyedropper-button::before {
   background-image: url(chrome://devtools/skin/images/command-eyedropper.svg);
 }
 
 /* Mix-in classes */
@@ -18,137 +18,161 @@
   background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
     linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
   background-size: 12px 12px;
   background-position: 0 0, 6px 6px;
 }
 
 .spectrum-slider-control {
   cursor: pointer;
-  box-shadow: 0 0 2px rgba(0,0,0,.6);
-  background: #fff;
+  box-shadow: 0 0 2px var(--theme-box-shadow-color);
+  background: var(--theme-slider-control-background);
   border-radius: 10px;
   opacity: .8;
 }
 
 .spectrum-box {
   border: 1px solid rgba(0,0,0,0.2);
   border-radius: 2px;
   background-clip: content-box;
 }
 
 /* Elements */
 
 #spectrum-tooltip {
-  padding: 4px;
+  padding: 5px;
+}
+
+/**
+ * Spectrum controls set the layout for the controls section of the color picker.
+ */
+.spectrum-controls {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 10px;
+  margin-right: 5px;
+  width: 200px;
 }
 
 .spectrum-container {
-  position: relative;
-  display: none;
-  top: 0;
-  left: 0;
-  border-radius: 0;
-  width: 200px;
-  padding: 5px;
+  display: flex;
+  flex-direction: column;
+  margin: -1px;
 }
 
-.spectrum-show {
-  display: inline-block;
+/**
+ * Spectrum color preview styles the color overlay over the checkered background image.
+ * It initializes the overlay and preview border colors for manipulation inside the
+ * Spectrum.prototype.updateColorPreview method.
+ */
+.spectrum-color-preview {
+  --overlay-color: transparent;
+  --preview-border-color: transparent;
+  --checkered-background: rgb(204, 204, 204);
+  border: 1px solid var(--preview-border-color);
+  border-radius: 50%;
+  width: 27px;
+  height: 27px;
+  background-color: #fff;
+  background-image: linear-gradient(
+    var(--overlay-color),
+    var(--overlay-color)),
+  linear-gradient(
+    45deg,
+    var(--checkered-background) 26%,
+    transparent 25%,
+    transparent 75%,
+    var(--checkered-background) 75%),
+  linear-gradient(
+    45deg,
+    var(--checkered-background) 26%,
+    transparent 25%,
+    transparent 75%,
+    var(--checkered-background) 75%);
+  background-size: 12px 12px;
+  background-position: 0 0, 6px 6px;
+}
+
+.spectrum-slider-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-around;
+  width: 130px;
+  margin-left: 10px;
+  height: 30px;
 }
 
 /* Keep aspect ratio:
 http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
-.spectrum-top {
+.spectrum-color-picker {
   position: relative;
-  width: 100%;
-  display: inline-block;
-}
-
-.spectrum-top-inner {
-  position: absolute;
-  top:0;
-  left:0;
-  bottom:0;
-  right:0;
+  width: 200px;
+  height: 120px;
 }
 
 .spectrum-color {
   position: absolute;
   top: 0;
   left: 0;
   bottom: 0;
-  right: 20%;
-}
-
-.spectrum-hue {
-  position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 83%;
-}
-
-.spectrum-fill {
-  /* Same as spectrum-color width */
-  margin-top: 85%;
+  width: 100%;
 }
 
 .spectrum-sat, .spectrum-val {
   position: absolute;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
 }
 
-.spectrum-dragger, .spectrum-slider {
-  -moz-user-select: none;
-}
-
-.spectrum-alpha {
+.spectrum-alpha,
+.spectrum-hue {
   position: relative;
   height: 8px;
   margin-top: 3px;
 }
 
-.spectrum-alpha-inner {
+.spectrum-alpha-inner,
+.spectrum-hue-inner {
   height: 100%;
 }
 
-.spectrum-alpha-handle {
+.spectrum-alpha-handle,
+.spectrum-hue-handle {
   position: absolute;
   top: -3px;
   bottom: -3px;
   width: 5px;
   left: 50%;
 }
 
 .spectrum-sat {
   background-image: linear-gradient(to right, #FFF, rgba(204, 154, 129, 0));
 }
 
 .spectrum-val {
   background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0));
 }
 
 .spectrum-hue {
-  background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+  background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
 }
 
 .spectrum-dragger {
+  -moz-user-select: none;
   position: absolute;
   top: 0px;
   left: 0px;
   cursor: pointer;
   border-radius: 50%;
   height: 8px;
   width: 8px;
-  border: 1px solid white;
-  box-shadow: 0 0 2px rgba(0,0,0,.6);
+  border: 1px solid var(--theme-tooltip-border);
+  box-shadow: 0 0 1px var(--theme-box-shadow-color);
 }
 
 .spectrum-slider {
   position: absolute;
   top: 0;
   height: 5px;
   left: -3px;
   right: -3px;
--- a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -55,29 +55,20 @@ class SwatchColorPickerTooltip extends S
     const container = doc.createElementNS(XHTML_NS, "div");
     container.id = "spectrum-tooltip";
 
     const node = doc.createElementNS(XHTML_NS, "div");
     node.id = "spectrum";
     container.appendChild(node);
 
     const widget = new Spectrum(node, color);
-    this.tooltip.setContent(container, { width: 218, height: 224 });
+    this.tooltip.setContent(container, { width: 210, height: 175 });
 
     widget.inspector = this.inspector;
 
-    const eyedropper = doc.createElementNS(XHTML_NS, "button");
-    eyedropper.id = "eyedropper-button";
-    eyedropper.className = "devtools-button";
-    /* pointerEvents for eyedropper has to be set auto to display tooltip when
-     * eyedropper is disabled in non-HTML documents.
-     */
-    eyedropper.style.pointerEvents = "auto";
-    container.appendChild(eyedropper);
-
     // Wait for the tooltip to be shown before calling widget.show
     // as it expect to be visible in order to compute DOM element sizes.
     this.tooltip.once("shown", () => {
       widget.show();
     });
 
     return widget;
   }
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -101,18 +101,21 @@
   --theme-arrowpanel-disabled-color: GrayText;
 
   /* Command line */
   --theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme);
   --theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme-focus);
 
   --theme-codemirror-gutter-background: #f4f4f4;
   --theme-messageCloseButtonFilter: invert(0);
+
+  /* Color Widget */
+  --theme-slider-control-background: #ffffff;
+  --theme-box-shadow-color: rgba(0,0,0,.6);
 }
-
 /*
  * For doorhangers elsewhere in Firefox, Mac uses fixed colors rather than
  * system colors.
  */
 :root[platform="mac"].theme-light {
   --theme-arrowpanel-background: hsla(0,0%,99%,.975);
   --theme-arrowpanel-color: hsl(0,0%,10%);
   --theme-arrowpanel-border-color: hsla(210,4%,10%,.05);
@@ -206,16 +209,20 @@
   --theme-arrowpanel-disabled-color: rgba(249,249,250,.5);
 
   /* Command line */
   --theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme);
   --theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme-focus);
 
   --theme-codemirror-gutter-background: #262b37;
   --theme-messageCloseButtonFilter: invert(1);
+
+  /* Color Widget */
+  --theme-slider-control-background: rgba(19, 28, 38, .85);
+  --theme-box-shadow-color: rgba(255, 255, 255);
 }
 
 :root {
   --theme-focus-border-color-textbox: #0675d3;
   --theme-textbox-box-shadow: rgba(97,181,255,.75);
 
   /* For accessibility purposes we want to enhance the focus styling. This
    * should improve keyboard navigation usability. */
--- a/devtools/shared/css/color.js
+++ b/devtools/shared/css/color.js
@@ -73,16 +73,17 @@ module.exports.colorUtils = {
   CssColor: CssColor,
   rgbToHsl: rgbToHsl,
   setAlpha: setAlpha,
   classifyColor: classifyColor,
   rgbToColorName: rgbToColorName,
   colorToRGBA: colorToRGBA,
   isValidCSSColor: isValidCSSColor,
   calculateContrastRatio: calculateContrastRatio,
+  calculateLuminance: calculateLuminance,
 };
 
 /**
  * Values used in COLOR_UNIT_PREF
  */
 CssColor.COLORUNIT = {
   "authored": "authored",
   "hex": "hex",