Bug 1332085 - Add Color Name field to Color Widget. r=gl
☠☠ backed out by b03d23d20564 ☠ ☠
authorSheldon Roddick <sheldon.roddick@hotmail.com>
Sun, 22 Jan 2017 09:45:23 -0700
changeset 375604 98530de82f5c6e0237b3656770d1bc87ac905a3a
parent 375603 2cded9d87d13294a1417a5bf46db6c64e3c7a951
child 375605 5e82725567cf5150ec751226a231619851e7f3b8
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1332085
milestone53.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 1332085 - Add Color Name field to Color Widget. r=gl
devtools/client/locales/en-US/colorwidget.properties
devtools/client/shared/test/browser.ini
devtools/client/shared/test/browser_color-widget-01.js
devtools/client/shared/widgets/ColorWidget.js
devtools/server/actors/highlighters/eye-dropper.js
devtools/shared/css/color.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/en-US/colorwidget.properties
@@ -0,0 +1,11 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used in the CSS Color Editor Widget
+# which can be found in a tooltip that appears in the Rules View when clicking
+# on a color swatch displayed next to CSS declarations like 'color: #FFF'.
+
+# LOCALIZATION NOTE (colorNameLabel): The label for the current color
+# widget's color name field
+colorNameLabel=Color Name:
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -20,16 +20,17 @@ support-files =
   html-mdn-css-syntax-old-style.html
   leakhunt.js
   test-actor.js
   test-actor-registry.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_css_angle.js]
 [browser_css_color.js]
+[browser_color-widget-01.js]
 [browser_cubic-bezier-01.js]
 [browser_cubic-bezier-02.js]
 [browser_cubic-bezier-03.js]
 [browser_cubic-bezier-04.js]
 [browser_cubic-bezier-05.js]
 [browser_cubic-bezier-06.js]
 [browser_filter-editor-01.js]
 [browser_filter-editor-02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_color-widget-01.js
@@ -0,0 +1,132 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the color widget color picker works correctly.
+
+const {ColorWidget} = require("devtools/client/shared/widgets/ColorWidget.js");
+
+const TEST_URI = `data:text/html,
+  <link rel="stylesheet" href="chrome://devtools/content/shared/widgets/color-widget.css" type="text/css"/>
+  <div id="color-widget-container" />`;
+
+add_task(function* () {
+  let [host,, doc] = yield createHost("bottom", TEST_URI);
+
+  let container = doc.getElementById("color-widget-container");
+
+  yield testCreateAndDestroyShouldAppendAndRemoveElements(container);
+  yield testPassingAColorAtInitShouldSetThatColor(container);
+  yield testSettingAndGettingANewColor(container);
+  yield testChangingColorShouldEmitEvents(container);
+  yield testSettingColorShoudUpdateTheUI(container);
+  yield testChangingColorShouldUpdateColorNameDisplay(container);
+
+  host.destroy();
+});
+
+function testCreateAndDestroyShouldAppendAndRemoveElements(container) {
+  ok(container, "We have the root node to append ColorWidget to");
+  is(container.childElementCount, 0, "Root node is empty");
+
+  let s = new ColorWidget(container, [255, 126, 255, 1]);
+  s.show();
+  ok(container.childElementCount > 0, "ColorWidget has appended elements");
+
+  s.destroy();
+  is(container.childElementCount, 0, "Destroying ColorWidget removed all nodes");
+}
+
+function testPassingAColorAtInitShouldSetThatColor(container) {
+  let initRgba = [255, 126, 255, 1];
+
+  let s = new ColorWidget(container, initRgba);
+  s.show();
+
+  let setRgba = s.rgb;
+
+  is(initRgba[0], setRgba[0], "ColorWidget initialized with the right color");
+  is(initRgba[1], setRgba[1], "ColorWidget initialized with the right color");
+  is(initRgba[2], setRgba[2], "ColorWidget initialized with the right color");
+  is(initRgba[3], setRgba[3], "ColorWidget initialized with the right color");
+
+  s.destroy();
+}
+
+function testSettingAndGettingANewColor(container) {
+  let s = new ColorWidget(container, [0, 0, 0, 1]);
+  s.show();
+
+  let colorToSet = [255, 255, 255, 1];
+  s.rgb = colorToSet;
+  let newColor = s.rgb;
+
+  is(colorToSet[0], newColor[0], "ColorWidget set with the right color");
+  is(colorToSet[1], newColor[1], "ColorWidget set with the right color");
+  is(colorToSet[2], newColor[2], "ColorWidget set with the right color");
+  is(colorToSet[3], newColor[3], "ColorWidget set with the right color");
+
+  s.destroy();
+}
+
+function testChangingColorShouldEmitEvents(container) {
+  return new Promise(resolve => {
+    let s = new ColorWidget(container, [255, 255, 255, 1]);
+    s.show();
+
+    s.once("changed", (event, rgba, color) => {
+      ok(true, "Changed event was emitted on color change");
+      is(rgba[0], 128, "New color is correct");
+      is(rgba[1], 64, "New color is correct");
+      is(rgba[2], 64, "New color is correct");
+      is(rgba[3], 1, "New color is correct");
+      is(`rgba(${rgba.join(", ")})`, color, "RGBA and css color correspond");
+
+      s.destroy();
+      resolve();
+    });
+
+    // Simulate a drag move event by calling the handler directly.
+    s.onDraggerMove(s.dragger.offsetWidth / 2, s.dragger.offsetHeight / 2);
+  });
+}
+
+function testSettingColorShoudUpdateTheUI(container) {
+  let s = new ColorWidget(container, [255, 255, 255, 1]);
+  s.show();
+  let dragHelperOriginalPos = [s.dragHelper.style.top, s.dragHelper.style.left];
+  let alphaHelperOriginalPos = s.alphaSliderHelper.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");
+
+  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");
+
+  s.destroy();
+}
+
+function testChangingColorShouldUpdateColorNameDisplay(container) {
+  let s = new ColorWidget(container, [255, 255, 255, 1]);
+  s.show();
+  s.updateUI();
+  is(s.colorName.textContent, "white", "Color Name should be white");
+
+  s.rgb = [0, 0, 0, 1];
+  s.updateUI();
+  is(s.colorName.textContent, "black", "Color Name should be black");
+
+  s.rgb = [0, 0, 1, 1];
+  s.updateUI();
+  is(s.colorName.textContent, "---", "Color Name should be ---");
+
+  s.destroy();
+}
--- a/devtools/client/shared/widgets/ColorWidget.js
+++ b/devtools/client/shared/widgets/ColorWidget.js
@@ -5,16 +5,22 @@
 /**
  * This file is a new working copy of Spectrum.js for the purposes of refreshing the color
  * widget. It is hidden behind a pref("devtools.inspector.colorWidget.enabled").
  */
 
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
+const {rgbToColorName} = require("devtools/shared/css/color").colorUtils;
+
+const { LocalizationHelper } = require("devtools/shared/l10n");
+const STRINGS_URI = "devtools/client/locales/colorwidget.properties";
+const L10N = new LocalizationHelper(STRINGS_URI);
+
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * ColorWidget creates a color picker widget in any container you give it.
  *
  * Simple usage example:
  *
  * const {ColorWidget} = require("devtools/client/shared/widgets/ColorWidget");
@@ -33,16 +39,18 @@ const XHTML_NS = "http://www.w3.org/1999
  * visible will allow the color widget to correctly initialize its various parts.
  *
  * Fires the following events:
  * - changed : When the user changes the current color
  */
 function ColorWidget(parentEl, rgb) {
   EventEmitter.decorate(this);
 
+  let colorNameLabel = L10N.getStr("colorNameLabel");
+
   this.element = parentEl.ownerDocument.createElementNS(XHTML_NS, "div");
   this.parentEl = parentEl;
 
   this.element.className = "colorwidget-container";
   this.element.innerHTML = `
     <div class="colorwidget-top">
       <div class="colorwidget-fill"></div>
       <div class="colorwidget-top-inner">
@@ -58,16 +66,20 @@ function ColorWidget(parentEl, rgb) {
         </div>
       </div>
     </div>
     <div class="colorwidget-alpha colorwidget-checker colorwidget-box">
       <div class="colorwidget-alpha-inner">
         <div class="colorwidget-alpha-handle colorwidget-slider-control"></div>
       </div>
     </div>
+    <div class="colorwidget-colorname">
+      <label class="colorwidget-colorname-label">${colorNameLabel}</label>
+      <span class="colorwidget-colorname-value"></span>
+    </div>
   `;
 
   this.onElementClick = this.onElementClick.bind(this);
   this.element.addEventListener("click", this.onElementClick);
 
   this.parentEl.appendChild(this.element);
 
   this.slider = this.element.querySelector(".colorwidget-hue");
@@ -78,16 +90,18 @@ function ColorWidget(parentEl, rgb) {
   this.dragHelper = this.element.querySelector(".colorwidget-dragger");
   ColorWidget.draggable(this.dragger, this.onDraggerMove.bind(this));
 
   this.alphaSlider = this.element.querySelector(".colorwidget-alpha");
   this.alphaSliderInner = this.element.querySelector(".colorwidget-alpha-inner");
   this.alphaSliderHelper = this.element.querySelector(".colorwidget-alpha-handle");
   ColorWidget.draggable(this.alphaSliderInner, this.onAlphaSliderMove.bind(this));
 
+  this.colorName = this.element.querySelector(".colorwidget-colorname-value");
+
   if (rgb) {
     this.rgb = rgb;
     this.updateUI();
   }
 }
 
 module.exports.ColorWidget = ColorWidget;
 
@@ -320,22 +334,26 @@ ColorWidget.prototype = {
 
     this.dragger.style.backgroundColor = flatColor;
 
     let rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
     let rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
     let alphaGradient = "linear-gradient(to right, " + rgbAlpha0 + ", " +
       rgbNoAlpha + ")";
     this.alphaSliderInner.style.background = alphaGradient;
+
+    let colorName = rgbToColorName(rgb[0], rgb[1], rgb[2]);
+    this.colorName.textContent = colorName || "---";
   },
 
   destroy: function () {
     this.element.removeEventListener("click", this.onElementClick);
 
     this.parentEl.removeChild(this.element);
 
     this.slider = null;
     this.dragger = null;
     this.alphaSlider = this.alphaSliderInner = this.alphaSliderHelper = null;
     this.parentEl = null;
     this.element = null;
+    this.colorName = null;
   }
 };
--- a/devtools/server/actors/highlighters/eye-dropper.js
+++ b/devtools/server/actors/highlighters/eye-dropper.js
@@ -502,22 +502,17 @@ function toColorString(rgb, format) {
     case "hex":
       return hexString(rgb);
     case "rgb":
       return "rgb(" + r + ", " + g + ", " + b + ")";
     case "hsl":
       let [h, s, l] = rgbToHsl(rgb);
       return "hsl(" + h + ", " + s + "%, " + l + "%)";
     case "name":
-      let str;
-      try {
-        str = rgbToColorName(r, g, b);
-      } catch (e) {
-        str = hexString(rgb);
-      }
+      let str = rgbToColorName(r, g, b) || hexString(rgb);
       return str;
     default:
       return hexString(rgb);
   }
 }
 
 /**
  * Produce a hex-formatted color string from rgb values.
--- a/devtools/shared/css/color.js
+++ b/devtools/shared/css/color.js
@@ -164,27 +164,24 @@ CssColor.prototype = {
   },
 
   get name() {
     let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
     if (invalidOrSpecialValue !== false) {
       return invalidOrSpecialValue;
     }
 
-    try {
-      let tuple = this._getRGBATuple();
+    let tuple = this._getRGBATuple();
 
-      if (tuple.a !== 1) {
-        return this.hex;
-      }
-      let {r, g, b} = tuple;
-      return rgbToColorName(r, g, b);
-    } catch (e) {
+    if (tuple.a !== 1) {
       return this.hex;
     }
+
+    let {r, g, b} = tuple;
+    return rgbToColorName(r, g, b) || this.hex;
   },
 
   get hex() {
     let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
     if (invalidOrSpecialValue !== false) {
       return invalidOrSpecialValue;
     }
     if (this.hasAlpha) {
@@ -532,37 +529,33 @@ function classifyColor(value) {
   return CssColor.COLORUNIT.name;
 }
 
 // This holds a map from colors back to color names for use by
 // rgbToColorName.
 var cssRGBMap;
 
 /**
- * Given a color, return its name, if it has one.  Throws an exception
- * if the color does not have a name.
+ * Given a color, return its name, if it has one. Otherwise
+ * return an empty string.
  *
  * @param {Number} r, g, b  The color components.
- * @return {String} the name of the color
+ * @return {String} the name of the color or an empty string
  */
 function rgbToColorName(r, g, b) {
   if (!cssRGBMap) {
     cssRGBMap = {};
     for (let name in cssColors) {
       let key = JSON.stringify(cssColors[name]);
       if (!(key in cssRGBMap)) {
         cssRGBMap[key] = name;
       }
     }
   }
-  let value = cssRGBMap[JSON.stringify([r, g, b, 1])];
-  if (!value) {
-    throw new Error("no such color");
-  }
-  return value;
+  return cssRGBMap[JSON.stringify([r, g, b, 1])] || "";
 }
 
 // Translated from nsColor.cpp.
 function _hslValue(m1, m2, h) {
   if (h < 0.0) {
     h += 1.0;
   }
   if (h > 1.0) {