Bug 1266842 - move css-color.js to devtools/client/shared; r?pbro draft
authorTom Tromey <tom@tromey.com>
Mon, 25 Apr 2016 14:26:34 -0600
changeset 356654 95b20853a4e82cfbbe585b7c0fc1868501549188
parent 356653 431e433efc38088f0627f808b1edc1cae70f05a2
child 356655 644a865e70f6d310fa3fe25af8c5856f06ca0873
push id16561
push userbmo:ttromey@mozilla.com
push dateTue, 26 Apr 2016 20:49:52 +0000
reviewerspbro
bugs1266842
milestone49.0a1
Bug 1266842 - move css-color.js to devtools/client/shared; r?pbro MozReview-Commit-ID: 3JttGia4hPW
.eslintignore
devtools/client/eyedropper/eyedropper.js
devtools/client/performance/modules/widgets/graphs.js
devtools/client/performance/modules/widgets/markers-overview.js
devtools/client/shared/css-color.js
devtools/client/shared/moz.build
devtools/client/shared/output-parser.js
devtools/client/shared/test/browser_css_color.js
devtools/client/shared/test/unit/test_cssColor.js
devtools/client/shared/test/unit/xpcshell.ini
devtools/client/shared/widgets/Tooltip.js
devtools/shared/css-color.js
devtools/shared/moz.build
devtools/shared/tests/unit/test_cssColor.js
devtools/shared/tests/unit/test_independent_loaders.js
devtools/shared/tests/unit/test_invisible_loader.js
devtools/shared/tests/unit/test_require.js
devtools/shared/tests/unit/xpcshell.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -96,25 +96,25 @@ devtools/client/netmonitor/test/**
 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/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
-!devtools/shared/css-color.js
 devtools/shared/*.jsm
 devtools/shared/apps/**
 devtools/shared/client/**
 devtools/shared/discovery/**
 devtools/shared/gcli/**
 devtools/shared/heapsnapshot/**
 devtools/shared/inspector/**
 devtools/shared/layout/**
--- a/devtools/client/eyedropper/eyedropper.js
+++ b/devtools/client/eyedropper/eyedropper.js
@@ -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/. */
 
 const {Cc, Ci, Cu} = require("chrome");
-const {rgbToHsl} = require("devtools/shared/css-color").colorUtils;
+const {rgbToHsl} = 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"]
--- a/devtools/client/performance/modules/widgets/graphs.js
+++ b/devtools/client/performance/modules/widgets/graphs.js
@@ -13,17 +13,17 @@ const { Heritage } = require("resource:/
 const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
 const BarGraphWidget = require("devtools/client/shared/widgets/BarGraphWidget");
 const MountainGraphWidget = require("devtools/client/shared/widgets/MountainGraphWidget");
 const { CanvasGraphUtils } = require("devtools/client/shared/widgets/Graphs");
 
 const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
 
-const { colorUtils } = require("devtools/shared/css-color");
+const { colorUtils } = require("devtools/client/shared/css-color");
 const { getColor } = require("devtools/client/shared/theme");
 const ProfilerGlobal = require("devtools/client/performance/modules/global");
 const { MarkersOverview } = require("devtools/client/performance/modules/widgets/markers-overview");
 const { createTierGraphDataFromFrameNode } = require("devtools/client/performance/modules/logic/jit");
 
 /**
  * For line graphs
  */
--- a/devtools/client/performance/modules/widgets/markers-overview.js
+++ b/devtools/client/performance/modules/widgets/markers-overview.js
@@ -8,17 +8,17 @@
  * the timeline data. Regions inside it may be selected, determining which
  * markers are visible in the "waterfall".
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { Heritage } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
 
-const { colorUtils } = require("devtools/shared/css-color");
+const { colorUtils } = require("devtools/client/shared/css-color");
 const { getColor } = require("devtools/client/shared/theme");
 const ProfilerGlobal = require("devtools/client/performance/modules/global");
 const { MarkerBlueprintUtils } = require("devtools/client/performance/modules/marker-blueprint-utils");
 const { TickUtils } = require("devtools/client/performance/modules/widgets/waterfall-ticks");
 const { TIMELINE_BLUEPRINT } = require("devtools/client/performance/modules/markers");
 
 const OVERVIEW_HEADER_HEIGHT = 14; // px
 const OVERVIEW_ROW_HEIGHT = 11; // px
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/css-color.js
@@ -0,0 +1,474 @@
+/* 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 {Cc, Ci} = require("chrome");
+const Services = require("Services");
+
+const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
+
+const SPECIALVALUES = new Set([
+  "currentcolor",
+  "initial",
+  "inherit",
+  "transparent",
+  "unset"
+]);
+
+/**
+ * This module is used to convert between various color types.
+ *
+ * Usage:
+ *   let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ *   let {colorUtils} = require("devtools/client/shared/css-color");
+ *   let color = new colorUtils.CssColor("red");
+ *
+ *   color.authored === "red"
+ *   color.hasAlpha === false
+ *   color.valid === true
+ *   color.transparent === false // transparent has a special status.
+ *   color.name === "red"        // returns hex or rgba when no name available.
+ *   color.hex === "#f00"        // returns shortHex when available else returns
+ *                                  longHex. If alpha channel is present then we
+ *                                  return this.rgba.
+ *   color.longHex === "#ff0000" // If alpha channel is present then we return
+ *                                  this.rgba.
+ *   color.rgb === "rgb(255, 0, 0)" // If alpha channel is present
+ *                                  // then we return this.rgba.
+ *   color.rgba === "rgba(255, 0, 0, 1)"
+ *   color.hsl === "hsl(0, 100%, 50%)"
+ *   color.hsla === "hsla(0, 100%, 50%, 1)" // If alpha channel is present
+ *                                             then we return this.rgba.
+ *
+ *   color.toString() === "#f00"; // Outputs the color type determined in the
+ *                                   COLOR_UNIT_PREF constant (above).
+ *   // Color objects can be reused
+ *   color.newColor("green") === "#0f0"; // true
+ *
+ *   Valid values for COLOR_UNIT_PREF are contained in CssColor.COLORUNIT.
+ */
+
+function CssColor(colorValue) {
+  this.newColor(colorValue);
+}
+
+module.exports.colorUtils = {
+  CssColor: CssColor,
+  rgbToHsl: rgbToHsl,
+  setAlpha: setAlpha,
+  classifyColor: classifyColor
+};
+
+/**
+ * Values used in COLOR_UNIT_PREF
+ */
+CssColor.COLORUNIT = {
+  "authored": "authored",
+  "hex": "hex",
+  "name": "name",
+  "rgb": "rgb",
+  "hsl": "hsl"
+};
+
+CssColor.prototype = {
+  _colorUnit: null,
+  _colorUnitUppercase: false,
+
+  // The value as-authored.
+  authored: null,
+  // A lower-cased copy of |authored|.
+  lowerCased: null,
+
+  get colorUnit() {
+    if (this._colorUnit === null) {
+      let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
+      this._colorUnit = CssColor.COLORUNIT[defaultUnit];
+      this._colorUnitUppercase =
+        (this.authored === this.authored.toUpperCase());
+    }
+    return this._colorUnit;
+  },
+
+  set colorUnit(unit) {
+    this._colorUnit = unit;
+  },
+
+  /**
+   * 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) {
+    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 true for all transparent values e.g. rgba(0, 0, 0, 0).
+   */
+  get transparent() {
+    try {
+      let tuple = this._getRGBATuple();
+      return !(tuple.r || tuple.g || tuple.b || tuple.a);
+    } catch (e) {
+      return false;
+    }
+  },
+
+  get specialValue() {
+    return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
+  },
+
+  get name() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+
+    try {
+      let tuple = this._getRGBATuple();
+
+      if (tuple.a !== 1) {
+        return this.rgb;
+      }
+      let {r, g, b} = tuple;
+      return DOMUtils.rgbToColorName(r, g, b);
+    } catch (e) {
+      return this.hex;
+    }
+  },
+
+  get hex() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (this.hasAlpha) {
+      return this.rgba;
+    }
+
+    let hex = this.longHex;
+    if (hex.charAt(1) == hex.charAt(2) &&
+        hex.charAt(3) == hex.charAt(4) &&
+        hex.charAt(5) == hex.charAt(6)) {
+      hex = "#" + hex.charAt(1) + hex.charAt(3) + hex.charAt(5);
+    }
+    return hex;
+  },
+
+  get longHex() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (this.hasAlpha) {
+      return this.rgba;
+    }
+
+    let tuple = this._getRGBATuple();
+    return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
+                  (tuple.b << 0)).toString(16).substr(-6);
+  },
+
+  get rgb() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (!this.hasAlpha) {
+      if (this.lowerCased.startsWith("rgb(")) {
+        // The color is valid and begins with rgb(.
+        return this.authored;
+      }
+      let tuple = this._getRGBATuple();
+      return "rgb(" + tuple.r + ", " + tuple.g + ", " + tuple.b + ")";
+    }
+    return this.rgba;
+  },
+
+  get rgba() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (this.lowerCased.startsWith("rgba(")) {
+      // The color is valid and begins with rgba(.
+      return this.authored;
+    }
+    let components = this._getRGBATuple();
+    return "rgba(" + components.r + ", " +
+                     components.g + ", " +
+                     components.b + ", " +
+                     components.a + ")";
+  },
+
+  get hsl() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (this.lowerCased.startsWith("hsl(")) {
+      // The color is valid and begins with hsl(.
+      return this.authored;
+    }
+    if (this.hasAlpha) {
+      return this.hsla;
+    }
+    return this._hsl();
+  },
+
+  get hsla() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (this.lowerCased.startsWith("hsla(")) {
+      // The color is valid and begins with hsla(.
+      return this.authored;
+    }
+    if (this.hasAlpha) {
+      let a = this._getRGBATuple().a;
+      return this._hsl(a);
+    }
+    return this._hsl(1);
+  },
+
+  /**
+   * Check whether the current color value is in the special list e.g.
+   * transparent or invalid.
+   *
+   * @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() {
+    if (this.specialValue) {
+      return this.specialValue;
+    }
+    if (!this.valid) {
+      return "";
+    }
+    return false;
+  },
+
+  /**
+   * Change color
+   *
+   * @param  {String} color
+   *         Any valid color string
+   */
+  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() {
+    // 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]];
+
+    for (let format of formats) {
+      if (this[format].toLowerCase() !== currentDisplayedColor.toLowerCase()) {
+        this.colorUnit = CssColor.COLORUNIT[format];
+        break;
+      }
+    }
+
+    return this.toString();
+  },
+
+  /**
+   * Return a string representing a color of type defined in COLOR_UNIT_PREF.
+   */
+  toString: function() {
+    let color;
+
+    switch (this.colorUnit) {
+      case CssColor.COLORUNIT.authored:
+        color = this.authored;
+        break;
+      case CssColor.COLORUNIT.hex:
+        color = this.hex;
+        break;
+      case CssColor.COLORUNIT.hsl:
+        color = this.hsl;
+        break;
+      case CssColor.COLORUNIT.name:
+        color = this.name;
+        break;
+      case CssColor.COLORUNIT.rgb:
+        color = this.rgb;
+        break;
+      default:
+        color = this.rgb;
+    }
+
+    if (this._colorUnitUppercase &&
+        this.colorUnit != CssColor.COLORUNIT.authored) {
+      color = color.toUpperCase();
+    }
+
+    return color;
+  },
+
+  /**
+   * Returns a RGBA 4-Tuple representation of a color or transparent as
+   * appropriate.
+   */
+  _getRGBATuple: function() {
+    let tuple = DOMUtils.colorToRGBA(this.authored);
+
+    tuple.a = parseFloat(tuple.a.toFixed(1));
+
+    return tuple;
+  },
+
+  _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() {
+    return this.rgba;
+  },
+};
+
+/**
+ * Convert rgb value to hsl
+ *
+ * @param {array} rgb
+ *         Array of rgb values
+ * @return {array}
+ *         Array of hsl values.
+ */
+function rgbToHsl([r, g, b]) {
+  r = r / 255;
+  g = g / 255;
+  b = b / 255;
+
+  let max = Math.max(r, g, b);
+  let min = Math.min(r, g, b);
+  let h;
+  let s;
+  let l = (max + min) / 2;
+
+  if (max == min) {
+    h = s = 0;
+  } else {
+    let d = max - min;
+    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+
+    switch (max) {
+      case r:
+        h = ((g - b) / d) % 6;
+        break;
+      case g:
+        h = (b - r) / d + 2;
+        break;
+      case b:
+        h = (r - g) / d + 4;
+        break;
+    }
+    h *= 60;
+    if (h < 0) {
+      h += 360;
+    }
+  }
+
+  return [Math.round(h), Math.round(s * 100), Math.round(l * 100)];
+}
+
+/**
+ * Takes a color value of any type (hex, hsl, hsla, rgb, rgba)
+ * and an alpha value to generate an rgba string with the correct
+ * alpha value.
+ *
+ * @param  {String} colorValue
+ *         Color in the form of hex, hsl, hsla, rgb, rgba.
+ * @param  {Number} alpha
+ *         Alpha value for the color, between 0 and 1.
+ * @return {String}
+ *         Converted color with `alpha` value in rgba form.
+ */
+function setAlpha(colorValue, alpha) {
+  let color = new CssColor(colorValue);
+
+  // Throw if the color supplied is not valid.
+  if (!color.valid) {
+    throw new Error("Invalid color.");
+  }
+
+  // If an invalid alpha valid, just set to 1.
+  if (!(alpha >= 0 && alpha <= 1)) {
+    alpha = 1;
+  }
+
+  let { r, g, b } = color._getRGBATuple();
+  return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
+}
+
+/**
+ * Given a color, classify its type as one of the possible color
+ * units, as known by |CssColor.colorUnit|.
+ *
+ * @param  {String} value
+ *         The color, in any form accepted by CSS.
+ * @return {String}
+ *         The color classification, one of "rgb", "hsl", "hex", or "name".
+ */
+function classifyColor(value) {
+  value = value.toLowerCase();
+  if (value.startsWith("rgb(") || value.startsWith("rgba(")) {
+    return CssColor.COLORUNIT.rgb;
+  } 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() {
+  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.js',
     'css-parsing-utils.js',
     'css-reload.js',
     'Curl.jsm',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
     'DOMHelpers.jsm',
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -1,17 +1,17 @@
 /* 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 {Cc, Ci, Cu} = require("chrome");
 const {angleUtils} = require("devtools/shared/css-angle");
-const {colorUtils} = require("devtools/shared/css-color");
+const {colorUtils} = require("devtools/client/shared/css-color");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const BEZIER_KEYWORDS = ["linear", "ease-in-out", "ease-in", "ease-out",
                          "ease"];
 
--- a/devtools/client/shared/test/browser_css_color.js
+++ b/devtools/client/shared/test/browser_css_color.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
-var {colorUtils} = require("devtools/shared/css-color");
+var {colorUtils} = require("devtools/client/shared/css-color");
 var origColorUnit;
 
 add_task(function*() {
   yield addTab("about:blank");
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
 
   info("Creating a test canvas element to test colors");
   let canvas = createTestCanvas(doc);
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_cssColor.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test classifyColor.
+
+"use strict";
+
+var Cu = Components.utils;
+var {require} = Cu.import("resource://devtools/shared/Loader.jsm");
+
+const {colorUtils} = require("devtools/client/shared/css-color");
+
+const CLASSIFY_TESTS = [
+  { input: "rgb(255,0,192)", output: "rgb" },
+  { input: "RGB(255,0,192)", output: "rgb" },
+  { input: "rgba(255,0,192, 0.25)", output: "rgb" },
+  { input: "hsl(5, 5, 5)", output: "hsl" },
+  { input: "hsla(5, 5, 5, 0.25)", output: "hsl" },
+  { input: "hSlA(5, 5, 5, 0.25)", output: "hsl" },
+  { input: "#f0c", output: "hex" },
+  { input: "#fe01cb", output: "hex" },
+  { input: "#FE01CB", output: "hex" },
+  { input: "blue", output: "name" },
+  { input: "orange", output: "name" }
+];
+
+function run_test() {
+  for (let test of CLASSIFY_TESTS) {
+    let result = colorUtils.classifyColor(test.input);
+    equal(result, test.output, "test classifyColor(" + test.input + ")");
+
+    let obj = new colorUtils.CssColor("purple");
+    obj.setAuthoredUnitFromColor(test.input);
+    equal(obj.colorUnit, test.output,
+          "test setAuthoredUnitFromColor(" + test.input + ")");
+  }
+}
--- a/devtools/client/shared/test/unit/xpcshell.ini
+++ b/devtools/client/shared/test/unit/xpcshell.ini
@@ -4,16 +4,17 @@ head =
 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_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]
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -7,17 +7,17 @@
 const {Cu, Ci} = require("chrome");
 const promise = require("promise");
 const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
 const {CubicBezierWidget} =
       require("devtools/client/shared/widgets/CubicBezierWidget");
 const {MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
 const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css-color");
+const {colorUtils} = require("devtools/client/shared/css-color");
 const Heritage = require("sdk/core/heritage");
 const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
 const Editor = require("devtools/client/sourceeditor/editor");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
deleted file mode 100644
--- a/devtools/shared/css-color.js
+++ /dev/null
@@ -1,474 +0,0 @@
-/* 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 {Cc, Ci} = require("chrome");
-const Services = require("Services");
-
-const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
-
-const SPECIALVALUES = new Set([
-  "currentcolor",
-  "initial",
-  "inherit",
-  "transparent",
-  "unset"
-]);
-
-/**
- * This module is used to convert between various color types.
- *
- * Usage:
- *   let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
- *   let {colorUtils} = require("devtools/shared/css-color");
- *   let color = new colorUtils.CssColor("red");
- *
- *   color.authored === "red"
- *   color.hasAlpha === false
- *   color.valid === true
- *   color.transparent === false // transparent has a special status.
- *   color.name === "red"        // returns hex or rgba when no name available.
- *   color.hex === "#f00"        // returns shortHex when available else returns
- *                                  longHex. If alpha channel is present then we
- *                                  return this.rgba.
- *   color.longHex === "#ff0000" // If alpha channel is present then we return
- *                                  this.rgba.
- *   color.rgb === "rgb(255, 0, 0)" // If alpha channel is present
- *                                  // then we return this.rgba.
- *   color.rgba === "rgba(255, 0, 0, 1)"
- *   color.hsl === "hsl(0, 100%, 50%)"
- *   color.hsla === "hsla(0, 100%, 50%, 1)" // If alpha channel is present
- *                                             then we return this.rgba.
- *
- *   color.toString() === "#f00"; // Outputs the color type determined in the
- *                                   COLOR_UNIT_PREF constant (above).
- *   // Color objects can be reused
- *   color.newColor("green") === "#0f0"; // true
- *
- *   Valid values for COLOR_UNIT_PREF are contained in CssColor.COLORUNIT.
- */
-
-function CssColor(colorValue) {
-  this.newColor(colorValue);
-}
-
-module.exports.colorUtils = {
-  CssColor: CssColor,
-  rgbToHsl: rgbToHsl,
-  setAlpha: setAlpha,
-  classifyColor: classifyColor
-};
-
-/**
- * Values used in COLOR_UNIT_PREF
- */
-CssColor.COLORUNIT = {
-  "authored": "authored",
-  "hex": "hex",
-  "name": "name",
-  "rgb": "rgb",
-  "hsl": "hsl"
-};
-
-CssColor.prototype = {
-  _colorUnit: null,
-  _colorUnitUppercase: false,
-
-  // The value as-authored.
-  authored: null,
-  // A lower-cased copy of |authored|.
-  lowerCased: null,
-
-  get colorUnit() {
-    if (this._colorUnit === null) {
-      let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
-      this._colorUnit = CssColor.COLORUNIT[defaultUnit];
-      this._colorUnitUppercase =
-        (this.authored === this.authored.toUpperCase());
-    }
-    return this._colorUnit;
-  },
-
-  set colorUnit(unit) {
-    this._colorUnit = unit;
-  },
-
-  /**
-   * 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) {
-    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 true for all transparent values e.g. rgba(0, 0, 0, 0).
-   */
-  get transparent() {
-    try {
-      let tuple = this._getRGBATuple();
-      return !(tuple.r || tuple.g || tuple.b || tuple.a);
-    } catch (e) {
-      return false;
-    }
-  },
-
-  get specialValue() {
-    return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
-  },
-
-  get name() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-
-    try {
-      let tuple = this._getRGBATuple();
-
-      if (tuple.a !== 1) {
-        return this.rgb;
-      }
-      let {r, g, b} = tuple;
-      return DOMUtils.rgbToColorName(r, g, b);
-    } catch (e) {
-      return this.hex;
-    }
-  },
-
-  get hex() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (this.hasAlpha) {
-      return this.rgba;
-    }
-
-    let hex = this.longHex;
-    if (hex.charAt(1) == hex.charAt(2) &&
-        hex.charAt(3) == hex.charAt(4) &&
-        hex.charAt(5) == hex.charAt(6)) {
-      hex = "#" + hex.charAt(1) + hex.charAt(3) + hex.charAt(5);
-    }
-    return hex;
-  },
-
-  get longHex() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (this.hasAlpha) {
-      return this.rgba;
-    }
-
-    let tuple = this._getRGBATuple();
-    return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
-                  (tuple.b << 0)).toString(16).substr(-6);
-  },
-
-  get rgb() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (!this.hasAlpha) {
-      if (this.lowerCased.startsWith("rgb(")) {
-        // The color is valid and begins with rgb(.
-        return this.authored;
-      }
-      let tuple = this._getRGBATuple();
-      return "rgb(" + tuple.r + ", " + tuple.g + ", " + tuple.b + ")";
-    }
-    return this.rgba;
-  },
-
-  get rgba() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (this.lowerCased.startsWith("rgba(")) {
-      // The color is valid and begins with rgba(.
-      return this.authored;
-    }
-    let components = this._getRGBATuple();
-    return "rgba(" + components.r + ", " +
-                     components.g + ", " +
-                     components.b + ", " +
-                     components.a + ")";
-  },
-
-  get hsl() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (this.lowerCased.startsWith("hsl(")) {
-      // The color is valid and begins with hsl(.
-      return this.authored;
-    }
-    if (this.hasAlpha) {
-      return this.hsla;
-    }
-    return this._hsl();
-  },
-
-  get hsla() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (this.lowerCased.startsWith("hsla(")) {
-      // The color is valid and begins with hsla(.
-      return this.authored;
-    }
-    if (this.hasAlpha) {
-      let a = this._getRGBATuple().a;
-      return this._hsl(a);
-    }
-    return this._hsl(1);
-  },
-
-  /**
-   * Check whether the current color value is in the special list e.g.
-   * transparent or invalid.
-   *
-   * @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() {
-    if (this.specialValue) {
-      return this.specialValue;
-    }
-    if (!this.valid) {
-      return "";
-    }
-    return false;
-  },
-
-  /**
-   * Change color
-   *
-   * @param  {String} color
-   *         Any valid color string
-   */
-  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() {
-    // 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]];
-
-    for (let format of formats) {
-      if (this[format].toLowerCase() !== currentDisplayedColor.toLowerCase()) {
-        this.colorUnit = CssColor.COLORUNIT[format];
-        break;
-      }
-    }
-
-    return this.toString();
-  },
-
-  /**
-   * Return a string representing a color of type defined in COLOR_UNIT_PREF.
-   */
-  toString: function() {
-    let color;
-
-    switch (this.colorUnit) {
-      case CssColor.COLORUNIT.authored:
-        color = this.authored;
-        break;
-      case CssColor.COLORUNIT.hex:
-        color = this.hex;
-        break;
-      case CssColor.COLORUNIT.hsl:
-        color = this.hsl;
-        break;
-      case CssColor.COLORUNIT.name:
-        color = this.name;
-        break;
-      case CssColor.COLORUNIT.rgb:
-        color = this.rgb;
-        break;
-      default:
-        color = this.rgb;
-    }
-
-    if (this._colorUnitUppercase &&
-        this.colorUnit != CssColor.COLORUNIT.authored) {
-      color = color.toUpperCase();
-    }
-
-    return color;
-  },
-
-  /**
-   * Returns a RGBA 4-Tuple representation of a color or transparent as
-   * appropriate.
-   */
-  _getRGBATuple: function() {
-    let tuple = DOMUtils.colorToRGBA(this.authored);
-
-    tuple.a = parseFloat(tuple.a.toFixed(1));
-
-    return tuple;
-  },
-
-  _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() {
-    return this.rgba;
-  },
-};
-
-/**
- * Convert rgb value to hsl
- *
- * @param {array} rgb
- *         Array of rgb values
- * @return {array}
- *         Array of hsl values.
- */
-function rgbToHsl([r, g, b]) {
-  r = r / 255;
-  g = g / 255;
-  b = b / 255;
-
-  let max = Math.max(r, g, b);
-  let min = Math.min(r, g, b);
-  let h;
-  let s;
-  let l = (max + min) / 2;
-
-  if (max == min) {
-    h = s = 0;
-  } else {
-    let d = max - min;
-    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
-
-    switch (max) {
-      case r:
-        h = ((g - b) / d) % 6;
-        break;
-      case g:
-        h = (b - r) / d + 2;
-        break;
-      case b:
-        h = (r - g) / d + 4;
-        break;
-    }
-    h *= 60;
-    if (h < 0) {
-      h += 360;
-    }
-  }
-
-  return [Math.round(h), Math.round(s * 100), Math.round(l * 100)];
-}
-
-/**
- * Takes a color value of any type (hex, hsl, hsla, rgb, rgba)
- * and an alpha value to generate an rgba string with the correct
- * alpha value.
- *
- * @param  {String} colorValue
- *         Color in the form of hex, hsl, hsla, rgb, rgba.
- * @param  {Number} alpha
- *         Alpha value for the color, between 0 and 1.
- * @return {String}
- *         Converted color with `alpha` value in rgba form.
- */
-function setAlpha(colorValue, alpha) {
-  let color = new CssColor(colorValue);
-
-  // Throw if the color supplied is not valid.
-  if (!color.valid) {
-    throw new Error("Invalid color.");
-  }
-
-  // If an invalid alpha valid, just set to 1.
-  if (!(alpha >= 0 && alpha <= 1)) {
-    alpha = 1;
-  }
-
-  let { r, g, b } = color._getRGBATuple();
-  return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
-}
-
-/**
- * Given a color, classify its type as one of the possible color
- * units, as known by |CssColor.colorUnit|.
- *
- * @param  {String} value
- *         The color, in any form accepted by CSS.
- * @return {String}
- *         The color classification, one of "rgb", "hsl", "hex", or "name".
- */
-function classifyColor(value) {
-  value = value.toLowerCase();
-  if (value.startsWith("rgb(") || value.startsWith("rgba(")) {
-    return CssColor.COLORUNIT.rgb;
-  } 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() {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -35,17 +35,16 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'async-storage.js',
     'async-utils.js',
     'content-observer.js',
     'css-angle.js',
-    'css-color.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'event-emitter.js',
     'event-parsers.js',
     'indentation.js',
     'Loader.jsm',
     'Parser.jsm',
     'path.js',
deleted file mode 100644
--- a/devtools/shared/tests/unit/test_cssColor.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Test classifyColor.
-
-"use strict";
-
-const {colorUtils} = require("devtools/shared/css-color");
-
-const CLASSIFY_TESTS = [
-  { input: "rgb(255,0,192)", output: "rgb" },
-  { input: "RGB(255,0,192)", output: "rgb" },
-  { input: "rgba(255,0,192, 0.25)", output: "rgb" },
-  { input: "hsl(5, 5, 5)", output: "hsl" },
-  { input: "hsla(5, 5, 5, 0.25)", output: "hsl" },
-  { input: "hSlA(5, 5, 5, 0.25)", output: "hsl" },
-  { input: "#f0c", output: "hex" },
-  { input: "#fe01cb", output: "hex" },
-  { input: "#FE01CB", output: "hex" },
-  { input: "blue", output: "name" },
-  { input: "orange", output: "name" }
-];
-
-function run_test() {
-  for (let test of CLASSIFY_TESTS) {
-    let result = colorUtils.classifyColor(test.input);
-    equal(result, test.output, "test classifyColor(" + test.input + ")");
-
-    let obj = new colorUtils.CssColor("purple");
-    obj.setAuthoredUnitFromColor(test.input);
-    equal(obj.colorUnit, test.output,
-          "test setAuthoredUnitFromColor(" + test.input + ")");
-  }
-}
--- a/devtools/shared/tests/unit/test_independent_loaders.js
+++ b/devtools/shared/tests/unit/test_independent_loaders.js
@@ -5,16 +5,16 @@
  * Ensure that each instance of the Dev Tools loader contains its own loader
  * instance, and also returns unique objects.  This ensures there is no sharing
  * in place between loaders.
  */
 function run_test() {
   let loader1 = new DevToolsLoader();
   let loader2 = new DevToolsLoader();
 
-  let color1 = loader1.require("devtools/shared/css-color");
-  let color2 = loader2.require("devtools/shared/css-color");
+  let indent1 = loader1.require("devtools/shared/indentation");
+  let indent2 = loader2.require("devtools/shared/indentation");
 
-  do_check_true(color1 !== color2);
+  do_check_true(indent1 !== indent2);
 
   do_check_true(loader1._provider !== loader2._provider);
   do_check_true(loader1._provider.loader !== loader2._provider.loader);
 }
--- a/devtools/shared/tests/unit/test_invisible_loader.js
+++ b/devtools/shared/tests/unit/test_invisible_loader.js
@@ -11,17 +11,17 @@ addDebuggerToGlobal(this);
 function run_test() {
   visible_loader();
   invisible_loader();
 }
 
 function visible_loader() {
   let loader = new DevToolsLoader();
   loader.invisibleToDebugger = false;
-  loader.require("devtools/shared/css-color");
+  loader.require("devtools/shared/indentation");
 
   let dbg = new Debugger();
   let sandbox = loader._provider.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_check_true(true);
   } catch(e) {
@@ -32,17 +32,17 @@ function visible_loader() {
   // Which is required to support unhandled promises rejection in mochitests
   const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
   do_check_eq(loader.require("promise"), promise);
 }
 
 function invisible_loader() {
   let loader = new DevToolsLoader();
   loader.invisibleToDebugger = true;
-  loader.require("devtools/shared/css-color");
+  loader.require("devtools/shared/indentation");
 
   let dbg = new Debugger();
   let sandbox = loader._provider.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_throw("debugger added invisible value");
   } catch(e) {
--- a/devtools/shared/tests/unit/test_require.js
+++ b/devtools/shared/tests/unit/test_require.js
@@ -4,17 +4,17 @@
 // Test require
 
 // Ensure that DevtoolsLoader.require doesn't spawn multiple
 // loader/modules when early cached
 function testBug1091706() {
   let loader = new DevToolsLoader();
   let require = loader.require;
 
-  let color1 = require("devtools/shared/css-color");
-  let color2 = require("devtools/shared/css-color");
+  let indent1 = require("devtools/shared/indentation");
+  let indent2 = require("devtools/shared/indentation");
 
-  do_check_true(color1 === color2);
+  do_check_true(indent1 === indent2);
 }
 
 function run_test() {
   testBug1091706();
 }
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -17,14 +17,13 @@ support-files =
 [test_independent_loaders.js]
 [test_invisible_loader.js]
 [test_isSet.js]
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
 [test_async-utils.js]
 [test_console_filtering.js]
 [test_cssAngle.js]
-[test_cssColor.js]
 [test_prettifyCSS.js]
 [test_require_lazy.js]
 [test_require.js]
 [test_stack.js]
 [test_executeSoon.js]