Bug 1266842 - replace rgbToColorName, colorToRGBA, isValidCSSColor in devtools; r?pbro draft
authorTom Tromey <tom@tromey.com>
Tue, 26 Apr 2016 09:24:20 -0600
changeset 356954 253e1832344035d4e03d9bf4fca09367c22dd0de
parent 356952 c9b34d1e28deb90fe0c40ccc63b825430766e1d4
child 519538 e5d2bd65862987c7582de37838cba9b0be7e7fe0
push id16661
push userbmo:ttromey@mozilla.com
push dateWed, 27 Apr 2016 18:57:57 +0000
reviewerspbro
bugs1266842
milestone49.0a1
Bug 1266842 - replace rgbToColorName, colorToRGBA, isValidCSSColor in devtools; r?pbro MozReview-Commit-ID: THnLJWYim4
.eslintignore
devtools/client/eyedropper/eyedropper.js
devtools/client/shared/css-color-db.js
devtools/client/shared/css-color.js
devtools/client/shared/moz.build
devtools/client/shared/output-parser.js
devtools/client/shared/test/unit/test_cssColorDatabase.js
devtools/client/shared/test/unit/xpcshell.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -97,16 +97,17 @@ devtools/client/netmonitor/har/test/**
 devtools/client/performance/**
 devtools/client/projecteditor/**
 devtools/client/promisedebugger/**
 devtools/client/responsivedesign/**
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/**
 !devtools/client/shared/css-color.js
+!devtools/client/shared/css-color-db.js
 devtools/client/sourceeditor/**
 devtools/client/webaudioeditor/**
 devtools/client/webconsole/**
 !devtools/client/webconsole/panel.js
 !devtools/client/webconsole/jsterm.js
 devtools/client/webide/**
 devtools/server/**
 devtools/shared/*.js
--- a/devtools/client/eyedropper/eyedropper.js
+++ b/devtools/client/eyedropper/eyedropper.js
@@ -1,14 +1,15 @@
 /* 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/. */
 
 const {Cc, Ci, Cu} = require("chrome");
-const {rgbToHsl} = require("devtools/client/shared/css-color").colorUtils;
+const {rgbToHsl, rgbToColorName} =
+      require("devtools/client/shared/css-color").colorUtils;
 const Telemetry = require("devtools/client/shared/telemetry");
 const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js");
 const promise = require("promise");
 const Services = require("Services");
 const {setTimeout, clearTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
 
 loader.lazyGetter(this, "clipboardHelper", function() {
   return Cc["@mozilla.org/widget/clipboardhelper;1"]
@@ -801,17 +802,17 @@ function toColorString(rgb, format) {
     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 = DOMUtils.rgbToColorName(r, g, b);
+        str = rgbToColorName(r, g, b);
       } catch(e) {
         str = hexString(rgb);
       }
       return str;
     default:
       return hexString(rgb);
   }
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/css-color-db.js
@@ -0,0 +1,162 @@
+/* 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";
+
+// /!\  Auto-generated from nsColorNameList.h.
+// This should be kept in sync with that list.
+// test_cssColorDatabase.js tries to enforce this.
+
+const cssColors = {
+  aliceblue: [240, 248, 255, 1],
+  antiquewhite: [250, 235, 215, 1],
+  aqua: [0, 255, 255, 1],
+  aquamarine: [127, 255, 212, 1],
+  azure: [240, 255, 255, 1],
+  beige: [245, 245, 220, 1],
+  bisque: [255, 228, 196, 1],
+  black: [0, 0, 0, 1],
+  blanchedalmond: [255, 235, 205, 1],
+  blue: [0, 0, 255, 1],
+  blueviolet: [138, 43, 226, 1],
+  brown: [165, 42, 42, 1],
+  burlywood: [222, 184, 135, 1],
+  cadetblue: [95, 158, 160, 1],
+  chartreuse: [127, 255, 0, 1],
+  chocolate: [210, 105, 30, 1],
+  coral: [255, 127, 80, 1],
+  cornflowerblue: [100, 149, 237, 1],
+  cornsilk: [255, 248, 220, 1],
+  crimson: [220, 20, 60, 1],
+  cyan: [0, 255, 255, 1],
+  darkblue: [0, 0, 139, 1],
+  darkcyan: [0, 139, 139, 1],
+  darkgoldenrod: [184, 134, 11, 1],
+  darkgray: [169, 169, 169, 1],
+  darkgreen: [0, 100, 0, 1],
+  darkgrey: [169, 169, 169, 1],
+  darkkhaki: [189, 183, 107, 1],
+  darkmagenta: [139, 0, 139, 1],
+  darkolivegreen: [85, 107, 47, 1],
+  darkorange: [255, 140, 0, 1],
+  darkorchid: [153, 50, 204, 1],
+  darkred: [139, 0, 0, 1],
+  darksalmon: [233, 150, 122, 1],
+  darkseagreen: [143, 188, 143, 1],
+  darkslateblue: [72, 61, 139, 1],
+  darkslategray: [47, 79, 79, 1],
+  darkslategrey: [47, 79, 79, 1],
+  darkturquoise: [0, 206, 209, 1],
+  darkviolet: [148, 0, 211, 1],
+  deeppink: [255, 20, 147, 1],
+  deepskyblue: [0, 191, 255, 1],
+  dimgray: [105, 105, 105, 1],
+  dimgrey: [105, 105, 105, 1],
+  dodgerblue: [30, 144, 255, 1],
+  firebrick: [178, 34, 34, 1],
+  floralwhite: [255, 250, 240, 1],
+  forestgreen: [34, 139, 34, 1],
+  fuchsia: [255, 0, 255, 1],
+  gainsboro: [220, 220, 220, 1],
+  ghostwhite: [248, 248, 255, 1],
+  gold: [255, 215, 0, 1],
+  goldenrod: [218, 165, 32, 1],
+  gray: [128, 128, 128, 1],
+  grey: [128, 128, 128, 1],
+  green: [0, 128, 0, 1],
+  greenyellow: [173, 255, 47, 1],
+  honeydew: [240, 255, 240, 1],
+  hotpink: [255, 105, 180, 1],
+  indianred: [205, 92, 92, 1],
+  indigo: [75, 0, 130, 1],
+  ivory: [255, 255, 240, 1],
+  khaki: [240, 230, 140, 1],
+  lavender: [230, 230, 250, 1],
+  lavenderblush: [255, 240, 245, 1],
+  lawngreen: [124, 252, 0, 1],
+  lemonchiffon: [255, 250, 205, 1],
+  lightblue: [173, 216, 230, 1],
+  lightcoral: [240, 128, 128, 1],
+  lightcyan: [224, 255, 255, 1],
+  lightgoldenrodyellow: [250, 250, 210, 1],
+  lightgray: [211, 211, 211, 1],
+  lightgreen: [144, 238, 144, 1],
+  lightgrey: [211, 211, 211, 1],
+  lightpink: [255, 182, 193, 1],
+  lightsalmon: [255, 160, 122, 1],
+  lightseagreen: [32, 178, 170, 1],
+  lightskyblue: [135, 206, 250, 1],
+  lightslategray: [119, 136, 153, 1],
+  lightslategrey: [119, 136, 153, 1],
+  lightsteelblue: [176, 196, 222, 1],
+  lightyellow: [255, 255, 224, 1],
+  lime: [0, 255, 0, 1],
+  limegreen: [50, 205, 50, 1],
+  linen: [250, 240, 230, 1],
+  magenta: [255, 0, 255, 1],
+  maroon: [128, 0, 0, 1],
+  mediumaquamarine: [102, 205, 170, 1],
+  mediumblue: [0, 0, 205, 1],
+  mediumorchid: [186, 85, 211, 1],
+  mediumpurple: [147, 112, 219, 1],
+  mediumseagreen: [60, 179, 113, 1],
+  mediumslateblue: [123, 104, 238, 1],
+  mediumspringgreen: [0, 250, 154, 1],
+  mediumturquoise: [72, 209, 204, 1],
+  mediumvioletred: [199, 21, 133, 1],
+  midnightblue: [25, 25, 112, 1],
+  mintcream: [245, 255, 250, 1],
+  mistyrose: [255, 228, 225, 1],
+  moccasin: [255, 228, 181, 1],
+  navajowhite: [255, 222, 173, 1],
+  navy: [0, 0, 128, 1],
+  oldlace: [253, 245, 230, 1],
+  olive: [128, 128, 0, 1],
+  olivedrab: [107, 142, 35, 1],
+  orange: [255, 165, 0, 1],
+  orangered: [255, 69, 0, 1],
+  orchid: [218, 112, 214, 1],
+  palegoldenrod: [238, 232, 170, 1],
+  palegreen: [152, 251, 152, 1],
+  paleturquoise: [175, 238, 238, 1],
+  palevioletred: [219, 112, 147, 1],
+  papayawhip: [255, 239, 213, 1],
+  peachpuff: [255, 218, 185, 1],
+  peru: [205, 133, 63, 1],
+  pink: [255, 192, 203, 1],
+  plum: [221, 160, 221, 1],
+  powderblue: [176, 224, 230, 1],
+  purple: [128, 0, 128, 1],
+  rebeccapurple: [102, 51, 153, 1],
+  red: [255, 0, 0, 1],
+  rosybrown: [188, 143, 143, 1],
+  royalblue: [65, 105, 225, 1],
+  saddlebrown: [139, 69, 19, 1],
+  salmon: [250, 128, 114, 1],
+  sandybrown: [244, 164, 96, 1],
+  seagreen: [46, 139, 87, 1],
+  seashell: [255, 245, 238, 1],
+  sienna: [160, 82, 45, 1],
+  silver: [192, 192, 192, 1],
+  skyblue: [135, 206, 235, 1],
+  slateblue: [106, 90, 205, 1],
+  slategray: [112, 128, 144, 1],
+  slategrey: [112, 128, 144, 1],
+  snow: [255, 250, 250, 1],
+  springgreen: [0, 255, 127, 1],
+  steelblue: [70, 130, 180, 1],
+  tan: [210, 180, 140, 1],
+  teal: [0, 128, 128, 1],
+  thistle: [216, 191, 216, 1],
+  tomato: [255, 99, 71, 1],
+  turquoise: [64, 224, 208, 1],
+  violet: [238, 130, 238, 1],
+  wheat: [245, 222, 179, 1],
+  white: [255, 255, 255, 1],
+  whitesmoke: [245, 245, 245, 1],
+  yellow: [255, 255, 0, 1],
+  yellowgreen: [154, 205, 50, 1],
+};
+
+exports.cssColors = cssColors;
--- a/devtools/client/shared/css-color.js
+++ b/devtools/client/shared/css-color.js
@@ -2,16 +2,18 @@
  * 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 {Cc, Ci} = require("chrome");
 const Services = require("Services");
 
+const {cssColors} = require("devtools/client/shared/css-color-db");
+
 const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
 
 const SPECIALVALUES = new Set([
   "currentcolor",
   "initial",
   "inherit",
   "transparent",
   "unset"
@@ -53,17 +55,20 @@ const SPECIALVALUES = new Set([
 function CssColor(colorValue) {
   this.newColor(colorValue);
 }
 
 module.exports.colorUtils = {
   CssColor: CssColor,
   rgbToHsl: rgbToHsl,
   setAlpha: setAlpha,
-  classifyColor: classifyColor
+  classifyColor: classifyColor,
+  rgbToColorName: rgbToColorName,
+  colorToRGBA: colorToRGBA,
+  isValidCSSColor: isValidCSSColor,
 };
 
 /**
  * Values used in COLOR_UNIT_PREF
  */
 CssColor.COLORUNIT = {
   "authored": "authored",
   "hex": "hex",
@@ -97,33 +102,33 @@ CssColor.prototype = {
 
   /**
    * If the current color unit pref is "authored", then set the
    * default color unit from the given color.  Otherwise, leave the
    * color unit untouched.
    *
    * @param {String} color The color to use
    */
-  setAuthoredUnitFromColor: function(color) {
+  setAuthoredUnitFromColor: function (color) {
     if (Services.prefs.getCharPref(COLOR_UNIT_PREF) ===
         CssColor.COLORUNIT.authored) {
       this._colorUnit = classifyColor(color);
       this._colorUnitUppercase = (color === color.toUpperCase());
     }
   },
 
   get hasAlpha() {
     if (!this.valid) {
       return false;
     }
     return this._getRGBATuple().a !== 1;
   },
 
   get valid() {
-    return DOMUtils.isValidCSSColor(this.authored);
+    return isValidCSSColor(this.authored);
   },
 
   /**
    * Return true for all transparent values e.g. rgba(0, 0, 0, 0).
    */
   get transparent() {
     try {
       let tuple = this._getRGBATuple();
@@ -145,17 +150,17 @@ CssColor.prototype = {
 
     try {
       let tuple = this._getRGBATuple();
 
       if (tuple.a !== 1) {
         return this.rgb;
       }
       let {r, g, b} = tuple;
-      return DOMUtils.rgbToColorName(r, g, b);
+      return rgbToColorName(r, g, b);
     } catch (e) {
       return this.hex;
     }
   },
 
   get hex() {
     let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
     if (invalidOrSpecialValue !== false) {
@@ -257,42 +262,42 @@ CssColor.prototype = {
    *
    * @return {String|Boolean}
    *         - If the current color is a special value e.g. "transparent" then
    *           return the color.
    *         - If the color is invalid return an empty string.
    *         - If the color is a regular color e.g. #F06 so we return false
    *           to indicate that the color is neither invalid or special.
    */
-  _getInvalidOrSpecialValue: function() {
+  _getInvalidOrSpecialValue: function () {
     if (this.specialValue) {
       return this.specialValue;
     }
     if (!this.valid) {
       return "";
     }
     return false;
   },
 
   /**
    * Change color
    *
    * @param  {String} color
    *         Any valid color string
    */
-  newColor: function(color) {
+  newColor: function (color) {
     // Store a lower-cased version of the color to help with format
     // testing.  The original text is kept as well so it can be
     // returned when needed.
     this.lowerCased = color.toLowerCase();
     this.authored = color;
     return this;
   },
 
-  nextColorUnit: function() {
+  nextColorUnit: function () {
     // Reorder the formats array to have the current format at the
     // front so we can cycle through.
     let formats = ["hex", "hsl", "rgb", "name"];
     let currentFormat = classifyColor(this.toString());
     let putOnEnd = formats.splice(0, formats.indexOf(currentFormat));
     formats = formats.concat(putOnEnd);
     let currentDisplayedColor = this[formats[0]];
 
@@ -304,17 +309,17 @@ CssColor.prototype = {
     }
 
     return this.toString();
   },
 
   /**
    * Return a string representing a color of type defined in COLOR_UNIT_PREF.
    */
-  toString: function() {
+  toString: function () {
     let color;
 
     switch (this.colorUnit) {
       case CssColor.COLORUNIT.authored:
         color = this.authored;
         break;
       case CssColor.COLORUNIT.hex:
         color = this.hex;
@@ -339,42 +344,42 @@ CssColor.prototype = {
 
     return color;
   },
 
   /**
    * Returns a RGBA 4-Tuple representation of a color or transparent as
    * appropriate.
    */
-  _getRGBATuple: function() {
-    let tuple = DOMUtils.colorToRGBA(this.authored);
+  _getRGBATuple: function () {
+    let tuple = colorToRGBA(this.authored);
 
     tuple.a = parseFloat(tuple.a.toFixed(1));
 
     return tuple;
   },
 
-  _hsl: function(maybeAlpha) {
+  _hsl: function (maybeAlpha) {
     if (this.lowerCased.startsWith("hsl(") && maybeAlpha === undefined) {
       // We can use it as-is.
       return this.authored;
     }
 
     let {r, g, b} = this._getRGBATuple();
     let [h, s, l] = rgbToHsl([r, g, b]);
     if (maybeAlpha !== undefined) {
       return "hsla(" + h + ", " + s + "%, " + l + "%, " + maybeAlpha + ")";
     }
     return "hsl(" + h + ", " + s + "%, " + l + "%)";
   },
 
   /**
    * This method allows comparison of CssColor objects using ===.
    */
-  valueOf: function() {
+  valueOf: function () {
     return this.rgba;
   },
 };
 
 /**
  * Convert rgb value to hsl
  *
  * @param {array} rgb
@@ -464,11 +469,190 @@ function classifyColor(value) {
   } else if (value.startsWith("hsl(") || value.startsWith("hsla(")) {
     return CssColor.COLORUNIT.hsl;
   } else if (/^#[0-9a-f]+$/.exec(value)) {
     return CssColor.COLORUNIT.hex;
   }
   return CssColor.COLORUNIT.name;
 }
 
-loader.lazyGetter(this, "DOMUtils", function() {
+// 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.
+ *
+ * @param {Number} r, g, b  The color components.
+ * @return {String} the name of the color
+ */
+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;
+}
+
+// Originally from dom/tests/mochitest/ajax/mochikit/MochiKit/Color.js.
+function _hslValue(n1, n2, hue) {
+  if (hue > 6.0) {
+    hue -= 6.0;
+  } else if (hue < 0.0) {
+    hue += 6.0;
+  }
+  let val;
+  if (hue < 1.0) {
+    val = n1 + (n2 - n1) * hue;
+  } else if (hue < 3.0) {
+    val = n2;
+  } else if (hue < 4.0) {
+    val = n1 + (n2 - n1) * (4.0 - hue);
+  } else {
+    val = n1;
+  }
+  return val;
+}
+
+// Originally from dom/tests/mochitest/ajax/mochikit/MochiKit/Color.js.
+function hslToRGB([hue, saturation, lightness]) {
+  let red;
+  let green;
+  let blue;
+  if (saturation === 0) {
+    red = lightness;
+    green = lightness;
+    blue = lightness;
+  } else {
+    let m2;
+    if (lightness <= 0.5) {
+      m2 = lightness * (1.0 + saturation);
+    } else {
+      m2 = lightness + saturation - (lightness * saturation);
+    }
+    let m1 = (2.0 * lightness) - m2;
+    let f = _hslValue;
+    let h6 = hue * 6.0;
+    red = f(m1, m2, h6 + 2);
+    green = f(m1, m2, h6);
+    blue = f(m1, m2, h6 - 2);
+  }
+  return [red, green, blue];
+}
+
+/**
+ * Convert a string representing a color to an object holding the
+ * color's components.  Any valid CSS color form can be passed in.
+ *
+ * @param {String} name the color
+ * @return {Object} an object of the form {r, g, b, a}; or null if the
+ *         name was not a valid color
+ */
+function colorToRGBA(name) {
+  name = name.trim().toLowerCase();
+
+  if (name in cssColors) {
+    let result = cssColors[name];
+    return {r: result[0], g: result[1], b: result[2], a: result[3]};
+  } else if (name === "transparent") {
+    return {r: 0, g: 0, b: 0, a: 0};
+  } else if (name === "currentcolor") {
+    return {r: 0, g: 0, b: 0, a: 1};
+  }
+
+  let lexer = DOMUtils.getCSSLexer(name);
+
+  let getToken = () => {
+    while (true) {
+      let token = lexer.nextToken();
+      if (!token || token.tokenType !== "comment" ||
+          token.tokenType !== "whitespace") {
+        return token;
+      }
+    }
+  };
+
+  let requireComma = (token) => {
+    if (token.tokenType !== "symbol" || token.text !== ",") {
+      return null;
+    }
+    return getToken();
+  };
+
+  let cap = (val, min, max) => {
+    if (val < min) {
+      val = min;
+    } else if (val > max) {
+      val = max;
+    }
+    return val;
+  };
+
+  let func = getToken();
+
+  const expectedFunctions = ["rgba", "rgba", "hsla", "hsl"];
+  if (!func || func.tokenType !== "function" ||
+      !expectedFunctions.includes(func.text)) {
+    return null;
+  }
+
+  let alpha = func.text === "rgba" || func.text === "hsla";
+  let vals = [];
+  for (let i = 0; i < 3; ++i) {
+    let token = getToken();
+    if (i > 0) {
+      token = requireComma(token);
+    }
+    if (token.tokenType !== "number" || !token.isInteger) {
+      return null;
+    }
+    vals.push(cap(token.number, 0, 255));
+  }
+
+  if (func.text === "hsl" || func.text === "hsla") {
+    vals = hslToRGB(vals);
+  }
+
+  if (alpha) {
+    let token = requireComma(getToken());
+    if (token.tokenType !== "number") {
+      return null;
+    }
+    vals.push(cap(token.number, 0, 1));
+  } else {
+    vals.push(1);
+  }
+
+  let parenToken = getToken();
+  if (!parenToken || parenToken.tokenType !== "symbol" ||
+      parenToken.text !== ")") {
+    return null;
+  }
+  if (getToken() !== null) {
+    return null;
+  }
+
+  return {r: vals[0], g: vals[1], b: vals[2], a: vals[3]};
+}
+
+/**
+ * Check whether a string names a valid CSS color.
+ *
+ * @param {String} name The string to check
+ * @return {Boolean} True if the string is a CSS color name.
+ */
+function isValidCSSColor(name) {
+  return colorToRGBA(name) !== null;
+}
+
+loader.lazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -13,16 +13,17 @@ DIRS += [
     'vendor',
     'widgets',
 ]
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
+    'css-color-db.js',
     'css-color.js',
     'css-parsing-utils.js',
     'css-reload.js',
     'Curl.jsm',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -202,43 +202,43 @@ OutputParser.prototype = {
             }
             ++parenDepth;
           } else {
             let functionText = this._collectFunctionText(token, text,
                                                          tokenStream);
 
             if (options.expectCubicBezier && token.text === "cubic-bezier") {
               this._appendCubicBezier(functionText, options);
-            } else if (colorOK() && DOMUtils.isValidCSSColor(functionText)) {
+            } else if (colorOK() && colorUtils.isValidCSSColor(functionText)) {
               this._appendColor(functionText, options);
             } else {
               this._appendTextNode(functionText);
             }
           }
           break;
         }
 
         case "ident":
           if (options.expectCubicBezier &&
               BEZIER_KEYWORDS.indexOf(token.text) >= 0) {
             this._appendCubicBezier(token.text, options);
-          } else if (colorOK() && DOMUtils.isValidCSSColor(token.text)) {
+          } else if (colorOK() && colorUtils.isValidCSSColor(token.text)) {
             this._appendColor(token.text, options);
           } else if (angleOK(token.text)) {
             this._appendAngle(token.text, options);
           } else {
             this._appendTextNode(text.substring(token.startOffset,
                                                 token.endOffset));
           }
           break;
 
         case "id":
         case "hash": {
           let original = text.substring(token.startOffset, token.endOffset);
-          if (colorOK() && DOMUtils.isValidCSSColor(original)) {
+          if (colorOK() && colorUtils.isValidCSSColor(original)) {
             this._appendColor(original, options);
           } else {
             this._appendTextNode(original);
           }
           break;
         }
         case "dimension":
           let value = text.substring(token.startOffset, token.endOffset);
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_cssColorDatabase.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that css-color-db matches platform.
+
+"use strict";
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+var {require} = Cu.import("resource://devtools/shared/Loader.jsm");
+
+loader.lazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
+
+const {colorUtils} = require("devtools/client/shared/css-color");
+const {cssColors} = require("devtools/client/shared/css-color-db");
+
+function isValid(colorName) {
+  ok(colorUtils.isValidCSSColor(colorName),
+     colorName + " is valid in database");
+  ok(DOMUtils.isValidCSSColor(colorName),
+     colorName + " is valid in DOMUtils");
+}
+
+function checkOne(colorName, checkName) {
+  let ours = colorUtils.colorToRGBA(colorName);
+  let fromDom = DOMUtils.colorToRGBA(colorName);
+  deepEqual(ours, fromDom, colorName + " agrees with DOMUtils");
+
+  isValid(colorName);
+
+  if (checkName) {
+    let {r, g, b} = ours;
+
+    // The color we got might not map back to the same name; but our
+    // implementation should agree with DOMUtils about which name is
+    // canonical.
+    let ourName = colorUtils.rgbToColorName(r, g, b);
+    let domName = DOMUtils.rgbToColorName(r, g, b);
+
+    equal(ourName, domName,
+          colorName + " canonical name agrees with DOMUtils");
+  }
+}
+
+function run_test() {
+  for (let name in cssColors) {
+    checkOne(name, true);
+  }
+  checkOne("transparent", false);
+
+  // Now check that platform didn't add a new name when we weren't
+  // looking.
+  let names = DOMUtils.getCSSValuesForProperty("background-color");
+  for (let name of names) {
+    if (name !== "hsl" && name !== "hsla" &&
+        name !== "rgb" && name !== "rgba" &&
+        name !== "inherit" && name !== "initial" && name !== "unset") {
+      checkOne(name, true);
+    }
+  }
+}
--- a/devtools/client/shared/test/unit/xpcshell.ini
+++ b/devtools/client/shared/test/unit/xpcshell.ini
@@ -5,16 +5,17 @@ tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_advanceValidate.js]
 [test_attribute-parsing-01.js]
 [test_attribute-parsing-02.js]
 [test_bezierCanvas.js]
 [test_cssColor.js]
+[test_cssColorDatabase.js]
 [test_cubicBezier.js]
 [test_escapeCSSComment.js]
 [test_parseDeclarations.js]
 [test_parsePseudoClassesAndAttributes.js]
 [test_parseSingleValue.js]
 [test_rewriteDeclarations.js]
 [test_source-utils.js]
 [test_suggestion-picker.js]