Bug 1290988 - Collect devtools/shared/css-* files into a folder; r=tromey
☠☠ backed out by 51d254b8787c ☠ ☠
authorGreg Tatum <tatum.creative@gmail.com>
Mon, 29 Aug 2016 11:02:34 -0500
changeset 313812 1bca17123507305c26103c9719a4795d140897bf
parent 313811 2320bcd12315f543b46ea2fb9a41d1fe202fae0d
child 313813 d67ee15cd8756594d994c3ab47cbb90acfc403d1
push id20532
push usercbook@mozilla.com
push dateWed, 14 Sep 2016 10:16:42 +0000
treeherderfx-team@39ebab543e1f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstromey
bugs1290988
milestone51.0a1
Bug 1290988 - Collect devtools/shared/css-* files into a folder; r=tromey In preparation for the additional files in the `mach generate-css-db` command, collect the CSS files into a folder. MozReview-Commit-ID: 9JRVsC2NMK8
.eslintignore
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-utils.js
devtools/client/inspector/rules/models/rule.js
devtools/client/inspector/rules/models/text-property.js
devtools/client/inspector/rules/views/rule-editor.js
devtools/client/inspector/rules/views/text-property-editor.js
devtools/client/inspector/shared/utils.js
devtools/client/performance/modules/widgets/graphs.js
devtools/client/performance/modules/widgets/markers-overview.js
devtools/client/shared/css-angle.js
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/test_cssColorDatabase.js
devtools/client/shared/test/unit/test_escapeCSSComment.js
devtools/client/shared/test/unit/test_parseDeclarations.js
devtools/client/shared/test/unit/test_parsePseudoClassesAndAttributes.js
devtools/client/shared/test/unit/test_parseSingleValue.js
devtools/client/shared/test/unit/test_rewriteDeclarations.js
devtools/client/shared/widgets/CubicBezierWidget.js
devtools/client/shared/widgets/FilterWidget.js
devtools/client/shared/widgets/MdnDocsWidget.js
devtools/client/shared/widgets/Tooltip.js
devtools/client/sourceeditor/css-autocompleter.js
devtools/server/actors/css-properties.js
devtools/server/actors/highlighters/eye-dropper.js
devtools/server/actors/styles.js
devtools/server/css-logic.js
devtools/server/tests/mochitest/test_css-properties_02.html
devtools/shared/css-color-db.js
devtools/shared/css-color.js
devtools/shared/css-lexer.js
devtools/shared/css-properties-db.js
devtools/shared/css/color-db.js
devtools/shared/css/color.js
devtools/shared/css/lexer.js
devtools/shared/css/moz.build
devtools/shared/css/parsing-utils.js
devtools/shared/css/properties-db.js
devtools/shared/fronts/css-properties.js
devtools/shared/fronts/styles.js
devtools/shared/inspector/css-logic.js
devtools/shared/moz.build
devtools/shared/tests/unit/test_css-properties-db.js
devtools/shared/tests/unit/test_csslexer.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -120,17 +120,16 @@ devtools/server/actors/**
 !devtools/server/actors/styles.js
 !devtools/server/actors/string.js
 !devtools/server/actors/csscoverage.js
 devtools/server/performance/**
 devtools/server/tests/**
 devtools/shared/*.js
 !devtools/shared/async-storage.js
 !devtools/shared/async-utils.js
-!devtools/shared/css-lexer.js
 !devtools/shared/defer.js
 !devtools/shared/event-emitter.js
 !devtools/shared/indentation.js
 !devtools/shared/loader-plugin-raw.jsm
 !devtools/shared/task.js
 devtools/shared/*.jsm
 !devtools/shared/Loader.jsm
 devtools/shared/apps/**
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -5,17 +5,17 @@
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
-  return require("devtools/shared/css-color").colorUtils;
+  return require("devtools/shared/css/color").colorUtils;
 });
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
   IconDetails,
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -13,17 +13,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
 
 XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
-  return require("devtools/shared/css-color").colorUtils;
+  return require("devtools/shared/css/color").colorUtils;
 });
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const POPUP_LOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -7,17 +7,17 @@
 "use strict";
 
 const promise = require("promise");
 const CssLogic = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const {TextProperty} =
       require("devtools/client/inspector/rules/models/text-property");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
-const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {parseDeclarations} = require("devtools/shared/css/parsing-utils");
 const Services = require("Services");
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools-shared/locale/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 /**
  * Rule is responsible for the following:
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -4,17 +4,17 @@
  * 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";
 
 /* eslint-disable mozilla/reject-some-requires */
 const {Cc, Ci} = require("chrome");
 /* eslint-enable mozilla/reject-some-requires */
-const {escapeCSSComment} = require("devtools/shared/css-parsing-utils");
+const {escapeCSSComment} = require("devtools/shared/css/parsing-utils");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 /* eslint-disable mozilla/reject-some-requires */
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 /* eslint-enable mozilla/reject-some-requires */
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -18,17 +18,17 @@ const {
   promiseWarn
 } = require("devtools/client/inspector/shared/utils");
 const {
   parseDeclarations,
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
-} = require("devtools/shared/css-parsing-utils");
+} = require("devtools/shared/css/parsing-utils");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools-shared/locale/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -12,17 +12,17 @@ const {
   createChild,
   appendText,
   advanceValidate,
   blurOnMultipleProperties
 } = require("devtools/client/inspector/shared/utils");
 const {
   parseDeclarations,
   parseSingleValue,
-} = require("devtools/shared/css-parsing-utils");
+} = require("devtools/shared/css/parsing-utils");
 const Services = require("Services");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const SHARED_SWATCH_CLASS = "ruleview-swatch";
 const COLOR_SWATCH_CLASS = "ruleview-colorswatch";
 const BEZIER_SWATCH_CLASS = "ruleview-bezierswatch";
 const FILTER_SWATCH_CLASS = "ruleview-filterswatch";
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -1,19 +1,19 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {parseDeclarations} = require("devtools/shared/css/parsing-utils");
 const promise = require("promise");
-const {getCSSLexer} = require("devtools/shared/css-lexer");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Create a child element with a set of attributes.
  *
  * @param {Element} parent
--- a/devtools/client/performance/modules/widgets/graphs.js
+++ b/devtools/client/performance/modules/widgets/graphs.js
@@ -11,17 +11,17 @@ const { Task } = require("devtools/share
 const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
 const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
 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/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
@@ -7,17 +7,17 @@
  * This file contains the "markers overview" graph, which is a minimap of all
  * the timeline data. Regions inside it may be selected, determining which
  * markers are visible in the "waterfall".
  */
 
 const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
 const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
 
-const { colorUtils } = require("devtools/shared/css-color");
+const { colorUtils } = require("devtools/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");
 
 // px
 const OVERVIEW_HEADER_HEIGHT = 14;
--- a/devtools/client/shared/css-angle.js
+++ b/devtools/client/shared/css-angle.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const SPECIALVALUES = new Set([
   "initial",
   "inherit",
   "unset"
 ]);
 
-const {getCSSLexer} = require("devtools/shared/css-lexer");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
 
 /**
  * This module is used to convert between various angle units.
  *
  * Usage:
  *   let {angleUtils} = require("devtools/client/shared/css-angle");
  *   let angle = new angleUtils.CssAngle("180deg");
  *
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -1,25 +1,25 @@
 /* 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 {angleUtils} = require("devtools/client/shared/css-angle");
-const {colorUtils} = require("devtools/shared/css-color");
-const {getCSSLexer} = require("devtools/shared/css-lexer");
+const {colorUtils} = require("devtools/shared/css/color");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {
   ANGLE_TAKING_FUNCTIONS,
   BEZIER_KEYWORDS,
   COLOR_TAKING_FUNCTIONS,
   CSS_TYPES
-} = require("devtools/shared/css-properties-db");
+} = require("devtools/shared/css/properties-db");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 loader.lazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 /**
@@ -35,17 +35,17 @@ loader.lazyGetter(this, "DOMUtils", func
  *   let parser = new OutputParser(document, supportsType);
  *
  *   parser.parseCssProperty("color", "red"); // Returns document fragment.
  *
  * @param {Document} document Used to create DOM nodes.
  * @param {Function} supportsTypes A function that returns a boolean when asked if a css
  * property name supports a given css type.
  * The function is executed like supportsType("color", CSS_TYPES.COLOR) where CSS_TYPES is
- * defined in devtools/shared/css-properties-db.js
+ * defined in devtools/shared/css/properties-db.js
  */
 function OutputParser(document, supportsType) {
   this.parsed = [];
   this.doc = document;
   this.supportsType = supportsType;
   this.colorSwatches = new WeakMap();
   this.angleSwatches = new WeakMap();
   this._onColorSwatchMouseDown = this._onColorSwatchMouseDown.bind(this);
--- 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/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);
--- a/devtools/client/shared/test/unit/test_cssColor.js
+++ b/devtools/client/shared/test/unit/test_cssColor.js
@@ -5,17 +5,17 @@
 
 "use strict";
 
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 
 var {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {colorUtils} = require("devtools/shared/css-color");
+const {colorUtils} = require("devtools/shared/css/color");
 
 loader.lazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 const CLASSIFY_TESTS = [
   { input: "rgb(255,0,192)", output: "rgb" },
   { input: "RGB(255,0,192)", output: "rgb" },
--- a/devtools/client/shared/test/unit/test_cssColorDatabase.js
+++ b/devtools/client/shared/test/unit/test_cssColorDatabase.js
@@ -8,18 +8,18 @@
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 
 var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 
-const {colorUtils} = require("devtools/shared/css-color");
-const {cssColors} = require("devtools/shared/css-color-db");
+const {colorUtils} = require("devtools/shared/css/color");
+const {cssColors} = require("devtools/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");
 }
 
--- a/devtools/client/shared/test/unit/test_escapeCSSComment.js
+++ b/devtools/client/shared/test/unit/test_escapeCSSComment.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {escapeCSSComment, _unescapeCSSComment} = require("devtools/shared/css-parsing-utils");
+const {escapeCSSComment, _unescapeCSSComment} = require("devtools/shared/css/parsing-utils");
 
 const TEST_DATA = [
   {
     input: "simple",
     expected: "simple"
   },
   {
     input: "/* comment */",
--- a/devtools/client/shared/test/unit/test_parseDeclarations.js
+++ b/devtools/client/shared/test/unit/test_parseDeclarations.js
@@ -2,17 +2,17 @@
 /* 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";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css-parsing-utils");
+const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css/parsing-utils");
 const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   // Simple test
   {
     input: "p:v;",
     expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
   },
--- a/devtools/client/shared/test/unit/test_parsePseudoClassesAndAttributes.js
+++ b/devtools/client/shared/test/unit/test_parsePseudoClassesAndAttributes.js
@@ -7,17 +7,17 @@
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
-} = require("devtools/shared/css-parsing-utils");
+} = require("devtools/shared/css/parsing-utils");
 
 const TEST_DATA = [
   // Test that a null input throws an exception
   {
     input: null,
     throws: true
   },
   // Test that a undefined input throws an exception
--- a/devtools/client/shared/test/unit/test_parseSingleValue.js
+++ b/devtools/client/shared/test/unit/test_parseSingleValue.js
@@ -2,17 +2,17 @@
 /* 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";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {parseSingleValue} = require("devtools/shared/css-parsing-utils");
+const {parseSingleValue} = require("devtools/shared/css/parsing-utils");
 const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   {input: null, throws: true},
   {input: undefined, throws: true},
   {input: "", expected: {value: "", priority: ""}},
   {input: "  \t \t \n\n  ", expected: {value: "", priority: ""}},
   {input: "blue", expected: {value: "blue", priority: ""}},
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {RuleRewriter} = require("devtools/shared/css-parsing-utils");
+const {RuleRewriter} = require("devtools/shared/css/parsing-utils");
 const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   {
     desc: "simple set",
     input: "p:v;",
     instruction: {type: "set", name: "p", value: "N", priority: "",
                   index: 0},
--- a/devtools/client/shared/widgets/CubicBezierWidget.js
+++ b/devtools/client/shared/widgets/CubicBezierWidget.js
@@ -26,17 +26,17 @@
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const {
   PREDEFINED,
   PRESETS,
   DEFAULT_PRESET_CATEGORY
 } = require("devtools/client/shared/widgets/CubicBezierPresets");
-const {getCSSLexer} = require("devtools/shared/css-lexer");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * CubicBezier data structure helper
  * Accepts an array of coordinates and exposes a few useful getters
  * @param {Array} coordinates i.e. [.42, 0, .58, 1]
  */
 function CubicBezier(coordinates) {
--- a/devtools/client/shared/widgets/FilterWidget.js
+++ b/devtools/client/shared/widgets/FilterWidget.js
@@ -12,17 +12,17 @@
 const EventEmitter = require("devtools/shared/event-emitter");
 const { Cc, Ci } = require("chrome");
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const STRINGS_URI = "devtools/locale/filterwidget.properties";
 const L10N = new LocalizationHelper(STRINGS_URI);
 
-const {cssTokenizer} = require("devtools/shared/css-parsing-utils");
+const {cssTokenizer} = require("devtools/shared/css/parsing-utils");
 
 const asyncStorage = require("devtools/shared/async-storage");
 
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 const DEFAULT_FILTER_TYPE = "length";
--- a/devtools/client/shared/widgets/MdnDocsWidget.js
+++ b/devtools/client/shared/widgets/MdnDocsWidget.js
@@ -21,17 +21,17 @@
  * document whose content is taken from MDN. If you want to embed
  * the content in a tooltip, use this in conjunction with Tooltip.js.
  */
 
 "use strict";
 
 const Services = require("Services");
 const defer = require("devtools/shared/defer");
-const {getCSSLexer} = require("devtools/shared/css-lexer");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {gDevTools} = require("devtools/client/framework/devtools");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/locale/inspector.properties");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -6,17 +6,17 @@
 
 const defer = require("devtools/shared/defer");
 const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
 const {CubicBezierWidget} =
       require("devtools/client/shared/widgets/CubicBezierWidget");
 const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
 const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/shared/css-color");
+const {colorUtils} = require("devtools/shared/css/color");
 const Heritage = require("sdk/core/heritage");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 const {Task} = require("devtools/shared/task");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const ESCAPE_KEYCODE = KeyCodes.DOM_VK_ESCAPE;
--- a/devtools/client/sourceeditor/css-autocompleter.js
+++ b/devtools/client/sourceeditor/css-autocompleter.js
@@ -1,16 +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";
 
 /* eslint-disable complexity */
-const {cssTokenizer, cssTokenizerWithLineColumn} = require("devtools/shared/css-parsing-utils");
+const {cssTokenizer, cssTokenizerWithLineColumn} = require("devtools/shared/css/parsing-utils");
 const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
 
 /**
  * Here is what this file (+ css-parsing-utils.js) do.
  *
  * The main objective here is to provide as much suggestions to the user editing
  * a stylesheet in Style Editor. The possible things that can be suggested are:
  *  - CSS property names
--- a/devtools/server/actors/css-properties.js
+++ b/devtools/server/actors/css-properties.js
@@ -12,18 +12,18 @@ loader.lazyGetter(this, "DOMUtils", () =
 
 loader.lazyGetter(this, "appInfo", () => {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
 });
 
 const protocol = require("devtools/shared/protocol");
 const { ActorClassWithSpec, Actor } = protocol;
 const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
-const { CSS_PROPERTIES, CSS_TYPES } = require("devtools/shared/css-properties-db");
-const { cssColors } = require("devtools/shared/css-color-db");
+const { CSS_PROPERTIES, CSS_TYPES } = require("devtools/shared/css/properties-db");
+const { cssColors } = require("devtools/shared/css/color-db");
 
 exports.CssPropertiesActor = ActorClassWithSpec(cssPropertiesSpec, {
   typeName: "cssProperties",
 
   initialize(conn, parent) {
     Actor.prototype.initialize.call(this, conn);
     this.parent = parent;
   },
--- a/devtools/server/actors/highlighters/eye-dropper.js
+++ b/devtools/server/actors/highlighters/eye-dropper.js
@@ -7,17 +7,17 @@
 // content page.
 // It basically displays a magnifier that tracks mouse moves and shows a magnified version
 // of the page. On click, it samples the color at the pixel being hovered.
 
 const {Ci, Cc} = require("chrome");
 const {CanvasFrameAnonymousContentHelper, createNode} = require("./utils/markup");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {rgbToHsl, rgbToColorName} = require("devtools/shared/css-color").colorUtils;
+const {rgbToHsl, rgbToColorName} = require("devtools/shared/css/color").colorUtils;
 const {getCurrentZoom, getFrameOffsets} = require("devtools/shared/layout/utils");
 
 loader.lazyGetter(this, "clipboardHelper",
   () => Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper));
 loader.lazyGetter(this, "l10n",
   () => Services.strings.createBundle("chrome://devtools/locale/eyedropper.properties"));
 
 const ZOOM_LEVEL_PREF = "devtools.eyedropper.zoom";
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {getDefinedGeometryProperties} = require("devtools/server/actors/highlighters/geometry-editor");
-const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {parseDeclarations} = require("devtools/shared/css/parsing-utils");
 const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 const {Task} = require("devtools/shared/task");
 const events = require("sdk/event/core");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
 const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets");
 const {pageStyleSpec, styleRuleSpec, ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 
--- a/devtools/server/css-logic.js
+++ b/devtools/server/css-logic.js
@@ -30,17 +30,17 @@
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { getRootBindingParent } = require("devtools/shared/layout/utils");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const {l10n, isContentStylesheet, shortSource, FILTER, STATUS} = require("devtools/shared/inspector/css-logic");
 
-loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css-lexer");
+loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css/lexer");
 
 /**
  * @param {function} isInherited A function that determines if the CSS property
  *                   is inherited.
  */
 function CssLogic(isInherited) {
   // The cache of examined CSS properties.
   this._isInherited = isInherited;
--- a/devtools/server/tests/mochitest/test_css-properties_02.html
+++ b/devtools/server/tests/mochitest/test_css-properties_02.html
@@ -9,17 +9,17 @@ Bug 1265798 - Replace inIDOMUtils.cssPro
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 window.onload = function() {
   const { initCssProperties, getCssProperties } =
     require("devtools/shared/fronts/css-properties");
 
-  const { CSS_PROPERTIES_DB } = require("devtools/shared/css-properties-db");
+  const { CSS_PROPERTIES_DB } = require("devtools/shared/css/properties-db");
 
   function promiseAttachUrl (url) {
     return new Promise((resolve, reject) => {
       attachURL(url, function(err, client, tab, doc) {
         if (err) {
           return reject(err);
         }
         resolve({client, tab, doc});
deleted file mode 100644
--- a/devtools/shared/css-color.js
+++ /dev/null
@@ -1,813 +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 Services = require("Services");
-
-const {getCSSLexer} = require("devtools/shared/css-lexer");
-const {cssColors} = require("devtools/shared/css-color-db");
-
-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 {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 when no name available.
- *   color.hex === "#f00"        // returns shortHex when available else returns
- *                                  longHex. If alpha channel is present then we
- *                                  return this.alphaHex if available,
- *                                  or this.longAlphaHex if not.
- *   color.alphaHex === "#f00f"  // returns short alpha hex when available
- *                                  else returns longAlphaHex.
- *   color.longHex === "#ff0000" // If alpha channel is present then we return
- *                                  this.longAlphaHex.
- *   color.longAlphaHex === "#ff0000ff"
- *   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,
-  rgbToColorName: rgbToColorName,
-  colorToRGBA: colorToRGBA,
-  isValidCSSColor: isValidCSSColor,
-};
-
-/**
- * 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 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.hex;
-      }
-      let {r, g, b} = tuple;
-      return 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.alphaHex;
-    }
-
-    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 alphaHex() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-
-    let alphaHex = this.longAlphaHex;
-    if (alphaHex.charAt(1) == alphaHex.charAt(2) &&
-        alphaHex.charAt(3) == alphaHex.charAt(4) &&
-        alphaHex.charAt(5) == alphaHex.charAt(6) &&
-        alphaHex.charAt(7) == alphaHex.charAt(8)) {
-      alphaHex = "#" + alphaHex.charAt(1) + alphaHex.charAt(3) +
-        alphaHex.charAt(5) + alphaHex.charAt(7);
-    }
-    return alphaHex;
-  },
-
-  get longHex() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-    if (this.hasAlpha) {
-      return this.longAlphaHex;
-    }
-
-    let tuple = this._getRGBATuple();
-    return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
-                  (tuple.b << 0)).toString(16).substr(-6);
-  },
-
-  get longAlphaHex() {
-    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
-    if (invalidOrSpecialValue !== false) {
-      return invalidOrSpecialValue;
-    }
-
-    let tuple = this._getRGBATuple();
-    return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
-                  (tuple.b << 0)).toString(16).substr(-6) +
-                  Math.round(tuple.a * 255).toString(16).padEnd(2, "0");
-  },
-
-  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 = 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;
-}
-
-// 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;
-}
-
-// Translated from nsColor.cpp.
-function _hslValue(m1, m2, h) {
-  if (h < 0.0) {
-    h += 1.0;
-  }
-  if (h > 1.0) {
-    h -= 1.0;
-  }
-  if (h < 1.0 / 6.0) {
-    return m1 + (m2 - m1) * h * 6.0;
-  }
-  if (h < 1.0 / 2.0) {
-    return m2;
-  }
-  if (h < 2.0 / 3.0) {
-    return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0;
-  }
-  return m1;
-}
-
-// Translated from nsColor.cpp.  All three values are expected to be
-// in the range 0-1.
-function hslToRGB([h, s, l]) {
-  let r, g, b;
-  let m1, m2;
-  if (l <= 0.5) {
-    m2 = l * (s + 1);
-  } else {
-    m2 = l + s - l * s;
-  }
-  m1 = l * 2 - m2;
-  r = Math.floor(255 * _hslValue(m1, m2, h + 1.0 / 3.0));
-  g = Math.floor(255 * _hslValue(m1, m2, h));
-  b = Math.floor(255 * _hslValue(m1, m2, h - 1.0 / 3.0));
-  return [r, g, b];
-}
-
-/**
- * A helper function to convert a hex string like "F0C" or "F0C8" to a color.
- *
- * @param {String} name the color string
- * @return {Object} an object of the form {r, g, b, a}; or null if the
- *         name was not a valid color
- */
-function hexToRGBA(name) {
-  let r, g, b, a = 1;
-
-  if (name.length === 3) {
-    // short hex string (e.g. F0C)
-    r = parseInt(name.charAt(0) + name.charAt(0), 16);
-    g = parseInt(name.charAt(1) + name.charAt(1), 16);
-    b = parseInt(name.charAt(2) + name.charAt(2), 16);
-  } else if (name.length === 4) {
-    // short alpha hex string (e.g. F0CA)
-    r = parseInt(name.charAt(0) + name.charAt(0), 16);
-    g = parseInt(name.charAt(1) + name.charAt(1), 16);
-    b = parseInt(name.charAt(2) + name.charAt(2), 16);
-    a = parseInt(name.charAt(3) + name.charAt(3), 16) / 255;
-  } else if (name.length === 6) {
-    // hex string (e.g. FD01CD)
-    r = parseInt(name.charAt(0) + name.charAt(1), 16);
-    g = parseInt(name.charAt(2) + name.charAt(3), 16);
-    b = parseInt(name.charAt(4) + name.charAt(5), 16);
-  } else if (name.length === 8) {
-    // alpha hex string (e.g. FD01CDAB)
-    r = parseInt(name.charAt(0) + name.charAt(1), 16);
-    g = parseInt(name.charAt(2) + name.charAt(3), 16);
-    b = parseInt(name.charAt(4) + name.charAt(5), 16);
-    a = parseInt(name.charAt(6) + name.charAt(7), 16) / 255;
-  } else {
-    return null;
-  }
-  a = Math.round(a * 10) / 10;
-  return {r, g, b, a};
-}
-
-/**
- * A helper function to clamp a value.
- *
- * @param {Number} value The value to clamp
- * @param {Number} min The minimum value
- * @param {Number} max The maximum value
- * @return {Number} A value between min and max
- */
-function clamp(value, min, max) {
-  if (value < min) {
-    value = min;
-  }
-  if (value > max) {
-    value = max;
-  }
-  return value;
-}
-
-/**
- * A helper function to get a token from a lexer, skipping comments
- * and whitespace.
- *
- * @param {CSSLexer} lexer The lexer
- * @return {CSSToken} The next non-whitespace, non-comment token; or
- * null at EOF.
- */
-function getToken(lexer) {
-  while (true) {
-    let token = lexer.nextToken();
-    if (!token || (token.tokenType !== "comment" &&
-                   token.tokenType !== "whitespace")) {
-      return token;
-    }
-  }
-}
-
-/**
- * A helper function to examine a token and ensure it is a comma.
- * Then fetch and return the next token.  Returns null if the
- * token was not a comma, or at EOF.
- *
- * @param {CSSLexer} lexer The lexer
- * @param {CSSToken} token A token to be examined
- * @return {CSSToken} The next non-whitespace, non-comment token; or
- * null if token was not a comma, or at EOF.
- */
-function requireComma(lexer, token) {
-  if (!token || token.tokenType !== "symbol" || token.text !== ",") {
-    return null;
-  }
-  return getToken(lexer);
-}
-
-/**
- * A helper function to parse the first three arguments to hsl()
- * or hsla().
- *
- * @param {CSSLexer} lexer The lexer
- * @return {Array} An array of the form [r,g,b]; or null on error.
- */
-function parseHsl(lexer) {
-  let vals = [];
-
-  let token = getToken(lexer);
-  if (!token || token.tokenType !== "number") {
-    return null;
-  }
-
-  let val = token.number / 360.0;
-  vals.push(val - Math.floor(val));
-
-  for (let i = 0; i < 2; ++i) {
-    token = requireComma(lexer, getToken(lexer));
-    if (!token || token.tokenType !== "percentage") {
-      return null;
-    }
-    vals.push(clamp(token.number, 0, 1));
-  }
-
-  return hslToRGB(vals);
-}
-
-/**
- * A helper function to parse the first three arguments to rgb()
- * or rgba().
- *
- * @param {CSSLexer} lexer The lexer
- * @return {Array} An array of the form [r,g,b]; or null on error.
- */
-function parseRgb(lexer) {
-  let isPercentage = false;
-  let vals = [];
-  for (let i = 0; i < 3; ++i) {
-    let token = getToken(lexer);
-    if (i > 0) {
-      token = requireComma(lexer, token);
-    }
-    if (!token) {
-      return null;
-    }
-
-    /* Either all parameters are integers, or all are percentages, so
-       check the first one to see.  */
-    if (i === 0 && token.tokenType === "percentage") {
-      isPercentage = true;
-    }
-
-    if (isPercentage) {
-      if (token.tokenType !== "percentage") {
-        return null;
-      }
-      vals.push(Math.round(255 * clamp(token.number, 0, 1)));
-    } else {
-      if (token.tokenType !== "number" || !token.isInteger) {
-        return null;
-      }
-      vals.push(clamp(token.number, 0, 255));
-    }
-  }
-  return vals;
-}
-
-/**
- * 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 = getCSSLexer(name);
-
-  let func = getToken(lexer);
-  if (!func) {
-    return null;
-  }
-
-  if (func.tokenType === "id" || func.tokenType === "hash") {
-    if (getToken(lexer) !== null) {
-      return null;
-    }
-    return hexToRGBA(func.text);
-  }
-
-  const expectedFunctions = ["rgba", "rgb", "hsla", "hsl"];
-  if (!func || func.tokenType !== "function" ||
-      !expectedFunctions.includes(func.text)) {
-    return null;
-  }
-
-  let hsl = func.text === "hsl" || func.text === "hsla";
-  let alpha = func.text === "rgba" || func.text === "hsla";
-
-  let vals = hsl ? parseHsl(lexer) : parseRgb(lexer);
-  if (!vals) {
-    return null;
-  }
-
-  if (alpha) {
-    let token = requireComma(lexer, getToken(lexer));
-    if (!token || token.tokenType !== "number") {
-      return null;
-    }
-    vals.push(clamp(token.number, 0, 1));
-  } else {
-    vals.push(1);
-  }
-
-  let parenToken = getToken(lexer);
-  if (!parenToken || parenToken.tokenType !== "symbol" ||
-      parenToken.text !== ")") {
-    return null;
-  }
-  if (getToken(lexer) !== 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;
-}
rename from devtools/shared/css-color-db.js
rename to devtools/shared/css/color-db.js
new file mode 100644
--- /dev/null
+++ b/devtools/shared/css/color.js
@@ -0,0 +1,813 @@
+/* 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 Services = require("Services");
+
+const {getCSSLexer} = require("devtools/shared/css/lexer");
+const {cssColors} = require("devtools/shared/css/color-db");
+
+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 {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 when no name available.
+ *   color.hex === "#f00"        // returns shortHex when available else returns
+ *                                  longHex. If alpha channel is present then we
+ *                                  return this.alphaHex if available,
+ *                                  or this.longAlphaHex if not.
+ *   color.alphaHex === "#f00f"  // returns short alpha hex when available
+ *                                  else returns longAlphaHex.
+ *   color.longHex === "#ff0000" // If alpha channel is present then we return
+ *                                  this.longAlphaHex.
+ *   color.longAlphaHex === "#ff0000ff"
+ *   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,
+  rgbToColorName: rgbToColorName,
+  colorToRGBA: colorToRGBA,
+  isValidCSSColor: isValidCSSColor,
+};
+
+/**
+ * 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 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.hex;
+      }
+      let {r, g, b} = tuple;
+      return 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.alphaHex;
+    }
+
+    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 alphaHex() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+
+    let alphaHex = this.longAlphaHex;
+    if (alphaHex.charAt(1) == alphaHex.charAt(2) &&
+        alphaHex.charAt(3) == alphaHex.charAt(4) &&
+        alphaHex.charAt(5) == alphaHex.charAt(6) &&
+        alphaHex.charAt(7) == alphaHex.charAt(8)) {
+      alphaHex = "#" + alphaHex.charAt(1) + alphaHex.charAt(3) +
+        alphaHex.charAt(5) + alphaHex.charAt(7);
+    }
+    return alphaHex;
+  },
+
+  get longHex() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+    if (this.hasAlpha) {
+      return this.longAlphaHex;
+    }
+
+    let tuple = this._getRGBATuple();
+    return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
+                  (tuple.b << 0)).toString(16).substr(-6);
+  },
+
+  get longAlphaHex() {
+    let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+    if (invalidOrSpecialValue !== false) {
+      return invalidOrSpecialValue;
+    }
+
+    let tuple = this._getRGBATuple();
+    return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
+                  (tuple.b << 0)).toString(16).substr(-6) +
+                  Math.round(tuple.a * 255).toString(16).padEnd(2, "0");
+  },
+
+  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 = 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;
+}
+
+// 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;
+}
+
+// Translated from nsColor.cpp.
+function _hslValue(m1, m2, h) {
+  if (h < 0.0) {
+    h += 1.0;
+  }
+  if (h > 1.0) {
+    h -= 1.0;
+  }
+  if (h < 1.0 / 6.0) {
+    return m1 + (m2 - m1) * h * 6.0;
+  }
+  if (h < 1.0 / 2.0) {
+    return m2;
+  }
+  if (h < 2.0 / 3.0) {
+    return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0;
+  }
+  return m1;
+}
+
+// Translated from nsColor.cpp.  All three values are expected to be
+// in the range 0-1.
+function hslToRGB([h, s, l]) {
+  let r, g, b;
+  let m1, m2;
+  if (l <= 0.5) {
+    m2 = l * (s + 1);
+  } else {
+    m2 = l + s - l * s;
+  }
+  m1 = l * 2 - m2;
+  r = Math.floor(255 * _hslValue(m1, m2, h + 1.0 / 3.0));
+  g = Math.floor(255 * _hslValue(m1, m2, h));
+  b = Math.floor(255 * _hslValue(m1, m2, h - 1.0 / 3.0));
+  return [r, g, b];
+}
+
+/**
+ * A helper function to convert a hex string like "F0C" or "F0C8" to a color.
+ *
+ * @param {String} name the color string
+ * @return {Object} an object of the form {r, g, b, a}; or null if the
+ *         name was not a valid color
+ */
+function hexToRGBA(name) {
+  let r, g, b, a = 1;
+
+  if (name.length === 3) {
+    // short hex string (e.g. F0C)
+    r = parseInt(name.charAt(0) + name.charAt(0), 16);
+    g = parseInt(name.charAt(1) + name.charAt(1), 16);
+    b = parseInt(name.charAt(2) + name.charAt(2), 16);
+  } else if (name.length === 4) {
+    // short alpha hex string (e.g. F0CA)
+    r = parseInt(name.charAt(0) + name.charAt(0), 16);
+    g = parseInt(name.charAt(1) + name.charAt(1), 16);
+    b = parseInt(name.charAt(2) + name.charAt(2), 16);
+    a = parseInt(name.charAt(3) + name.charAt(3), 16) / 255;
+  } else if (name.length === 6) {
+    // hex string (e.g. FD01CD)
+    r = parseInt(name.charAt(0) + name.charAt(1), 16);
+    g = parseInt(name.charAt(2) + name.charAt(3), 16);
+    b = parseInt(name.charAt(4) + name.charAt(5), 16);
+  } else if (name.length === 8) {
+    // alpha hex string (e.g. FD01CDAB)
+    r = parseInt(name.charAt(0) + name.charAt(1), 16);
+    g = parseInt(name.charAt(2) + name.charAt(3), 16);
+    b = parseInt(name.charAt(4) + name.charAt(5), 16);
+    a = parseInt(name.charAt(6) + name.charAt(7), 16) / 255;
+  } else {
+    return null;
+  }
+  a = Math.round(a * 10) / 10;
+  return {r, g, b, a};
+}
+
+/**
+ * A helper function to clamp a value.
+ *
+ * @param {Number} value The value to clamp
+ * @param {Number} min The minimum value
+ * @param {Number} max The maximum value
+ * @return {Number} A value between min and max
+ */
+function clamp(value, min, max) {
+  if (value < min) {
+    value = min;
+  }
+  if (value > max) {
+    value = max;
+  }
+  return value;
+}
+
+/**
+ * A helper function to get a token from a lexer, skipping comments
+ * and whitespace.
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @return {CSSToken} The next non-whitespace, non-comment token; or
+ * null at EOF.
+ */
+function getToken(lexer) {
+  while (true) {
+    let token = lexer.nextToken();
+    if (!token || (token.tokenType !== "comment" &&
+                   token.tokenType !== "whitespace")) {
+      return token;
+    }
+  }
+}
+
+/**
+ * A helper function to examine a token and ensure it is a comma.
+ * Then fetch and return the next token.  Returns null if the
+ * token was not a comma, or at EOF.
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @param {CSSToken} token A token to be examined
+ * @return {CSSToken} The next non-whitespace, non-comment token; or
+ * null if token was not a comma, or at EOF.
+ */
+function requireComma(lexer, token) {
+  if (!token || token.tokenType !== "symbol" || token.text !== ",") {
+    return null;
+  }
+  return getToken(lexer);
+}
+
+/**
+ * A helper function to parse the first three arguments to hsl()
+ * or hsla().
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @return {Array} An array of the form [r,g,b]; or null on error.
+ */
+function parseHsl(lexer) {
+  let vals = [];
+
+  let token = getToken(lexer);
+  if (!token || token.tokenType !== "number") {
+    return null;
+  }
+
+  let val = token.number / 360.0;
+  vals.push(val - Math.floor(val));
+
+  for (let i = 0; i < 2; ++i) {
+    token = requireComma(lexer, getToken(lexer));
+    if (!token || token.tokenType !== "percentage") {
+      return null;
+    }
+    vals.push(clamp(token.number, 0, 1));
+  }
+
+  return hslToRGB(vals);
+}
+
+/**
+ * A helper function to parse the first three arguments to rgb()
+ * or rgba().
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @return {Array} An array of the form [r,g,b]; or null on error.
+ */
+function parseRgb(lexer) {
+  let isPercentage = false;
+  let vals = [];
+  for (let i = 0; i < 3; ++i) {
+    let token = getToken(lexer);
+    if (i > 0) {
+      token = requireComma(lexer, token);
+    }
+    if (!token) {
+      return null;
+    }
+
+    /* Either all parameters are integers, or all are percentages, so
+       check the first one to see.  */
+    if (i === 0 && token.tokenType === "percentage") {
+      isPercentage = true;
+    }
+
+    if (isPercentage) {
+      if (token.tokenType !== "percentage") {
+        return null;
+      }
+      vals.push(Math.round(255 * clamp(token.number, 0, 1)));
+    } else {
+      if (token.tokenType !== "number" || !token.isInteger) {
+        return null;
+      }
+      vals.push(clamp(token.number, 0, 255));
+    }
+  }
+  return vals;
+}
+
+/**
+ * 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 = getCSSLexer(name);
+
+  let func = getToken(lexer);
+  if (!func) {
+    return null;
+  }
+
+  if (func.tokenType === "id" || func.tokenType === "hash") {
+    if (getToken(lexer) !== null) {
+      return null;
+    }
+    return hexToRGBA(func.text);
+  }
+
+  const expectedFunctions = ["rgba", "rgb", "hsla", "hsl"];
+  if (!func || func.tokenType !== "function" ||
+      !expectedFunctions.includes(func.text)) {
+    return null;
+  }
+
+  let hsl = func.text === "hsl" || func.text === "hsla";
+  let alpha = func.text === "rgba" || func.text === "hsla";
+
+  let vals = hsl ? parseHsl(lexer) : parseRgb(lexer);
+  if (!vals) {
+    return null;
+  }
+
+  if (alpha) {
+    let token = requireComma(lexer, getToken(lexer));
+    if (!token || token.tokenType !== "number") {
+      return null;
+    }
+    vals.push(clamp(token.number, 0, 1));
+  } else {
+    vals.push(1);
+  }
+
+  let parenToken = getToken(lexer);
+  if (!parenToken || parenToken.tokenType !== "symbol" ||
+      parenToken.text !== ")") {
+    return null;
+  }
+  if (getToken(lexer) !== 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;
+}
rename from devtools/shared/css-lexer.js
rename to devtools/shared/css/lexer.js
new file mode 100644
--- /dev/null
+++ b/devtools/shared/css/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'color-db.js',
+    'color.js',
+    'lexer.js',
+    'parsing-utils.js',
+    'properties-db.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/shared/css/parsing-utils.js
@@ -0,0 +1,1113 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// This file holds various CSS parsing and rewriting utilities.
+// Some entry points of note are:
+// parseDeclarations - parse a CSS rule into declarations
+// RuleRewriter - rewrite CSS rule text
+// parsePseudoClassesAndAttributes - parse selector and extract
+//     pseudo-classes
+// parseSingleValue - parse a single CSS property value
+
+"use strict";
+
+const promise = require("promise");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
+const {Task} = require("devtools/shared/task");
+
+const SELECTOR_ATTRIBUTE = exports.SELECTOR_ATTRIBUTE = 1;
+const SELECTOR_ELEMENT = exports.SELECTOR_ELEMENT = 2;
+const SELECTOR_PSEUDO_CLASS = exports.SELECTOR_PSEUDO_CLASS = 3;
+
+// Used to test whether a newline appears anywhere in some text.
+const NEWLINE_RX = /[\r\n]/;
+// Used to test whether a bit of text starts an empty comment, either
+// an "ordinary" /* ... */ comment, or a "heuristic bypass" comment
+// like /*! ... */.
+const EMPTY_COMMENT_START_RX = /^\/\*!?[ \r\n\t\f]*$/;
+// Used to test whether a bit of text ends an empty comment.
+const EMPTY_COMMENT_END_RX = /^[ \r\n\t\f]*\*\//;
+// Used to test whether a string starts with a blank line.
+const BLANK_LINE_RX = /^[ \t]*(?:\r\n|\n|\r|\f|$)/;
+
+// When commenting out a declaration, we put this character into the
+// comment opener so that future parses of the commented text know to
+// bypass the property name validity heuristic.
+const COMMENT_PARSING_HEURISTIC_BYPASS_CHAR = "!";
+
+/**
+ * A generator function that lexes a CSS source string, yielding the
+ * CSS tokens.  Comment tokens are dropped.
+ *
+ * @param {String} CSS source string
+ * @yield {CSSToken} The next CSSToken that is lexed
+ * @see CSSToken for details about the returned tokens
+ */
+function* cssTokenizer(string) {
+  let lexer = getCSSLexer(string);
+  while (true) {
+    let token = lexer.nextToken();
+    if (!token) {
+      break;
+    }
+    // None of the existing consumers want comments.
+    if (token.tokenType !== "comment") {
+      yield token;
+    }
+  }
+}
+
+/**
+ * Pass |string| to the CSS lexer and return an array of all the
+ * returned tokens.  Comment tokens are not included.  In addition to
+ * the usual information, each token will have starting and ending
+ * line and column information attached.  Specifically, each token
+ * has an additional "loc" attribute.  This attribute is an object
+ * of the form {line: L, column: C}.  Lines and columns are both zero
+ * based.
+ *
+ * It's best not to add new uses of this function.  In general it is
+ * simpler and better to use the CSSToken offsets, rather than line
+ * and column.  Also, this function lexes the entire input string at
+ * once, rather than lazily yielding a token stream.  Use
+ * |cssTokenizer| or |getCSSLexer| instead.
+ *
+ * @param{String} string The input string.
+ * @return {Array} An array of tokens (@see CSSToken) that have
+ *        line and column information.
+ */
+function cssTokenizerWithLineColumn(string) {
+  let lexer = getCSSLexer(string);
+  let result = [];
+  let prevToken = undefined;
+  while (true) {
+    let token = lexer.nextToken();
+    let lineNumber = lexer.lineNumber;
+    let columnNumber = lexer.columnNumber;
+
+    if (prevToken) {
+      prevToken.loc.end = {
+        line: lineNumber,
+        column: columnNumber
+      };
+    }
+
+    if (!token) {
+      break;
+    }
+
+    if (token.tokenType === "comment") {
+      // We've already dealt with the previous token's location.
+      prevToken = undefined;
+    } else {
+      let startLoc = {
+        line: lineNumber,
+        column: columnNumber
+      };
+      token.loc = {start: startLoc};
+
+      result.push(token);
+      prevToken = token;
+    }
+  }
+
+  return result;
+}
+
+/**
+ * Escape a comment body.  Find the comment start and end strings in a
+ * string and inserts backslashes so that the resulting text can
+ * itself be put inside a comment.
+ *
+ * @param {String} inputString
+ *                 input string
+ * @return {String} the escaped result
+ */
+function escapeCSSComment(inputString) {
+  let result = inputString.replace(/\/(\\*)\*/g, "/\\$1*");
+  return result.replace(/\*(\\*)\//g, "*\\$1/");
+}
+
+/**
+ * Un-escape a comment body.  This undoes any comment escaping that
+ * was done by escapeCSSComment.  That is, given input like "/\*
+ * comment *\/", it will strip the backslashes.
+ *
+ * @param {String} inputString
+ *                 input string
+ * @return {String} the un-escaped result
+ */
+function unescapeCSSComment(inputString) {
+  let result = inputString.replace(/\/\\(\\*)\*/g, "/$1*");
+  return result.replace(/\*\\(\\*)\//g, "*$1/");
+}
+
+/**
+ * A helper function for @see parseDeclarations that handles parsing
+ * of comment text.  This wraps a recursive call to parseDeclarations
+ * with the processing needed to ensure that offsets in the result
+ * refer back to the original, unescaped, input string.
+ *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ * @param {String} commentText The text of the comment, without the
+ *                             delimiters.
+ * @param {Number} startOffset The offset of the comment opener
+ *                             in the original text.
+ * @param {Number} endOffset The offset of the comment closer
+ *                           in the original text.
+ * @return {array} Array of declarations of the same form as returned
+ *                 by parseDeclarations.
+ */
+function parseCommentDeclarations(isCssPropertyKnown, commentText, startOffset,
+                                  endOffset) {
+  let commentOverride = false;
+  if (commentText === "") {
+    return [];
+  } else if (commentText[0] === COMMENT_PARSING_HEURISTIC_BYPASS_CHAR) {
+    // This is the special sign that the comment was written by
+    // rewriteDeclarations and so we should bypass the usual
+    // heuristic.
+    commentOverride = true;
+    commentText = commentText.substring(1);
+  }
+
+  let rewrittenText = unescapeCSSComment(commentText);
+
+  // We might have rewritten an embedded comment.  For example
+  // /\* ... *\/ would turn into /* ... */.
+  // This rewriting is necessary for proper lexing, but it means
+  // that the offsets we get back can be off.  So now we compute
+  // a map so that we can rewrite offsets later.  The map is the same
+  // length as |rewrittenText| and tells us how to map an index
+  // into |rewrittenText| to an index into |commentText|.
+  //
+  // First, we find the location of each comment starter or closer in
+  // |rewrittenText|.  At these spots we put a 1 into |rewrites|.
+  // Then we walk the array again, using the elements to compute a
+  // delta, which we use to make the final mapping.
+  //
+  // Note we allocate one extra entry because we can see an ending
+  // offset that is equal to the length.
+  let rewrites = new Array(rewrittenText.length + 1).fill(0);
+
+  let commentRe = /\/\\*\*|\*\\*\//g;
+  while (true) {
+    let matchData = commentRe.exec(rewrittenText);
+    if (!matchData) {
+      break;
+    }
+    rewrites[matchData.index] = 1;
+  }
+
+  let delta = 0;
+  for (let i = 0; i <= rewrittenText.length; ++i) {
+    delta += rewrites[i];
+    // |startOffset| to add the offset from the comment starter, |+2|
+    // for the length of the "/*", then |i| and |delta| as described
+    // above.
+    rewrites[i] = startOffset + 2 + i + delta;
+    if (commentOverride) {
+      ++rewrites[i];
+    }
+  }
+
+  // Note that we pass "false" for parseComments here.  It doesn't
+  // seem worthwhile to support declarations in comments-in-comments
+  // here, as there's no way to generate those using the tools, and
+  // users would be crazy to write such things.
+  let newDecls = parseDeclarationsInternal(isCssPropertyKnown, rewrittenText,
+                                           false, true, commentOverride);
+  for (let decl of newDecls) {
+    decl.offsets[0] = rewrites[decl.offsets[0]];
+    decl.offsets[1] = rewrites[decl.offsets[1]];
+    decl.colonOffsets[0] = rewrites[decl.colonOffsets[0]];
+    decl.colonOffsets[1] = rewrites[decl.colonOffsets[1]];
+    decl.commentOffsets = [startOffset, endOffset];
+  }
+  return newDecls;
+}
+
+/**
+ * A helper function for parseDeclarationsInternal that creates a new
+ * empty declaration.
+ *
+ * @return {object} an empty declaration of the form returned by
+ *                  parseDeclarations
+ */
+function getEmptyDeclaration() {
+  return {name: "", value: "", priority: "",
+          terminator: "",
+          offsets: [undefined, undefined],
+          colonOffsets: false};
+}
+
+/**
+ * A helper function that does all the parsing work for
+ * parseDeclarations.  This is separate because it has some arguments
+ * that don't make sense in isolation.
+ *
+ * The return value and arguments are like parseDeclarations, with
+ * these additional arguments.
+ *
+ * @param {Function} isCssPropertyKnown
+ *        Function to check if the CSS property is known.
+ * @param {Boolean} inComment
+ *        If true, assume that this call is parsing some text
+ *        which came from a comment in another declaration.
+ *        In this case some heuristics are used to avoid parsing
+ *        text which isn't obviously a series of declarations.
+ * @param {Boolean} commentOverride
+ *        This only makes sense when inComment=true.
+ *        When true, assume that the comment was generated by
+ *        rewriteDeclarations, and skip the usual name-checking
+ *        heuristic.
+ */
+function parseDeclarationsInternal(isCssPropertyKnown, inputString,
+                                   parseComments, inComment, commentOverride) {
+  if (inputString === null || inputString === undefined) {
+    throw new Error("empty input string");
+  }
+
+  let lexer = getCSSLexer(inputString);
+
+  let declarations = [getEmptyDeclaration()];
+  let lastProp = declarations[0];
+
+  let current = "", hasBang = false;
+  while (true) {
+    let token = lexer.nextToken();
+    if (!token) {
+      break;
+    }
+
+    // Ignore HTML comment tokens (but parse anything they might
+    // happen to surround).
+    if (token.tokenType === "htmlcomment") {
+      continue;
+    }
+
+    // Update the start and end offsets of the declaration, but only
+    // when we see a significant token.
+    if (token.tokenType !== "whitespace" && token.tokenType !== "comment") {
+      if (lastProp.offsets[0] === undefined) {
+        lastProp.offsets[0] = token.startOffset;
+      }
+      lastProp.offsets[1] = token.endOffset;
+    } else if (lastProp.name && !current && !hasBang &&
+               !lastProp.priority && lastProp.colonOffsets[1]) {
+      // Whitespace appearing after the ":" is attributed to it.
+      lastProp.colonOffsets[1] = token.endOffset;
+    }
+
+    if (token.tokenType === "symbol" && token.text === ":") {
+      if (!lastProp.name) {
+        // Set the current declaration name if there's no name yet
+        lastProp.name = current.trim();
+        lastProp.colonOffsets = [token.startOffset, token.endOffset];
+        current = "";
+        hasBang = false;
+
+        // When parsing a comment body, if the left-hand-side is not a
+        // valid property name, then drop it and stop parsing.
+        if (inComment && !commentOverride &&
+            !isCssPropertyKnown(lastProp.name)) {
+          lastProp.name = null;
+          break;
+        }
+      } else {
+        // Otherwise, just append ':' to the current value (declaration value
+        // with colons)
+        current += ":";
+      }
+    } else if (token.tokenType === "symbol" && token.text === ";") {
+      lastProp.terminator = "";
+      // When parsing a comment, if the name hasn't been set, then we
+      // have probably just seen an ordinary semicolon used in text,
+      // so drop this and stop parsing.
+      if (inComment && !lastProp.name) {
+        current = "";
+        break;
+      }
+      lastProp.value = current.trim();
+      current = "";
+      hasBang = false;
+      declarations.push(getEmptyDeclaration());
+      lastProp = declarations[declarations.length - 1];
+    } else if (token.tokenType === "ident") {
+      if (token.text === "important" && hasBang) {
+        lastProp.priority = "important";
+        hasBang = false;
+      } else {
+        if (hasBang) {
+          current += "!";
+        }
+        // Re-escape the token to avoid dequoting problems.
+        // See bug 1287620.
+        current += CSS.escape(token.text);
+      }
+    } else if (token.tokenType === "symbol" && token.text === "!") {
+      hasBang = true;
+    } else if (token.tokenType === "whitespace") {
+      if (current !== "") {
+        current += " ";
+      }
+    } else if (token.tokenType === "comment") {
+      if (parseComments && !lastProp.name && !lastProp.value) {
+        let commentText = inputString.substring(token.startOffset + 2,
+                                                token.endOffset - 2);
+        let newDecls = parseCommentDeclarations(isCssPropertyKnown, commentText,
+                                                token.startOffset,
+                                                token.endOffset);
+
+        // Insert the new declarations just before the final element.
+        let lastDecl = declarations.pop();
+        declarations = [...declarations, ...newDecls, lastDecl];
+      } else {
+        current += " ";
+      }
+    } else {
+      current += inputString.substring(token.startOffset, token.endOffset);
+    }
+  }
+
+  // Handle whatever trailing properties or values might still be there
+  if (current) {
+    if (!lastProp.name) {
+      // Ignore this case in comments.
+      if (!inComment) {
+        // Trailing property found, e.g. p1:v1;p2:v2;p3
+        lastProp.name = current.trim();
+      }
+    } else {
+      // Trailing value found, i.e. value without an ending ;
+      lastProp.value = current.trim();
+      let terminator = lexer.performEOFFixup("", true);
+      lastProp.terminator = terminator + ";";
+      // If the input was unterminated, attribute the remainder to
+      // this property.  This avoids some bad behavior when rewriting
+      // an unterminated comment.
+      if (terminator) {
+        lastProp.offsets[1] = inputString.length;
+      }
+    }
+  }
+
+  // Remove declarations that have neither a name nor a value
+  declarations = declarations.filter(prop => prop.name || prop.value);
+
+  return declarations;
+}
+
+/**
+ * Returns an array of CSS declarations given a string.
+ * For example, parseDeclarations(isCssPropertyKnown, "width: 1px; height: 1px")
+ * would return:
+ * [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
+ *
+ * The input string is assumed to only contain declarations so { and }
+ * characters will be treated as part of either the property or value,
+ * depending where it's found.
+ *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server.
+ * @param {String} inputString
+ *        An input string of CSS
+ * @param {Boolean} parseComments
+ *        If true, try to parse the contents of comments as well.
+ *        A comment will only be parsed if it occurs outside of
+ *        the body of some other declaration.
+ * @return {Array} an array of objects with the following signature:
+ *         [{"name": string, "value": string, "priority": string,
+ *           "terminator": string,
+ *           "offsets": [start, end], "colonOffsets": [start, end]},
+ *          ...]
+ *         Here, "offsets" holds the offsets of the start and end
+ *         of the declaration text, in a form suitable for use with
+ *         String.substring.
+ *         "terminator" is a string to use to terminate the declaration,
+ *         usually "" to mean no additional termination is needed.
+ *         "colonOffsets" holds the start and end locations of the
+ *         ":" that separates the property name from the value.
+ *         If the declaration appears in a comment, then there will
+ *         be an additional {"commentOffsets": [start, end] property
+ *         on the object, which will hold the offsets of the start
+ *         and end of the enclosing comment.
+ */
+function parseDeclarations(isCssPropertyKnown, inputString,
+                           parseComments = false) {
+  return parseDeclarationsInternal(isCssPropertyKnown, inputString,
+                                   parseComments, false, false);
+}
+
+/**
+ * Return an object that can be used to rewrite declarations in some
+ * source text.  The source text and parsing are handled in the same
+ * way as @see parseDeclarations, with |parseComments| being true.
+ * Rewriting is done by calling one of the modification functions like
+ * setPropertyEnabled.  The returned object has the same interface
+ * as @see RuleModificationList.
+ *
+ * An example showing how to disable the 3rd property in a rule:
+ *
+ *    let rewriter = new RuleRewriter(isCssPropertyKnown, ruleActor,
+ *                                    ruleActor.authoredText);
+ *    rewriter.setPropertyEnabled(3, "color", false);
+ *    rewriter.apply().then(() => { ... the change is made ... });
+ *
+ * The exported rewriting methods are |renameProperty|, |setPropertyEnabled|,
+ * |createProperty|, |setProperty|, and |removeProperty|.  The |apply|
+ * method can be used to send the edited text to the StyleRuleActor;
+ * |getDefaultIndentation| is useful for the methods requiring a
+ * default indentation value; and |getResult| is useful for testing.
+ *
+ * Additionally, editing will set the |changedDeclarations| property
+ * on this object.  This property has the same form as the |changed|
+ * property of the object returned by |getResult|.
+ *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server. Note that if Bug 1222047
+ *        is completed then isCssPropertyKnown will not need to be passed in.
+ *        The CssProperty front will be able to obtained directly from the
+ *        RuleRewriter.
+ * @param {StyleRuleFront} rule The style rule to use.  Note that this
+ *        is only needed by the |apply| and |getDefaultIndentation| methods;
+ *        and in particular for testing it can be |null|.
+ * @param {String} inputString The CSS source text to parse and modify.
+ * @return {Object} an object that can be used to rewrite the input text.
+ */
+function RuleRewriter(isCssPropertyKnown, rule, inputString) {
+  this.rule = rule;
+  this.inputString = inputString;
+  // Whether there are any newlines in the input text.
+  this.hasNewLine = /[\r\n]/.test(this.inputString);
+  // Keep track of which any declarations we had to rewrite while
+  // performing the requested action.
+  this.changedDeclarations = {};
+  // The declarations.
+  this.declarations = parseDeclarations(isCssPropertyKnown, this.inputString,
+                                        true);
+
+  this.decl = null;
+  this.result = null;
+  // If not null, a promise that must be wait upon before |apply| can
+  // do its work.
+  this.editPromise = null;
+
+  // If the |defaultIndentation| property is set, then it is used;
+  // otherwise the RuleRewriter will try to compute the default
+  // indentation based on the style sheet's text.  This override
+  // facility is for testing.
+  this.defaultIndentation = null;
+}
+
+RuleRewriter.prototype = {
+  /**
+   * An internal function to complete initialization and set some
+   * properties for further processing.
+   *
+   * @param {Number} index The index of the property to modify
+   */
+  completeInitialization: function (index) {
+    if (index < 0) {
+      throw new Error("Invalid index " + index + ". Expected positive integer");
+    }
+    // |decl| is the declaration to be rewritten, or null if there is no
+    // declaration corresponding to |index|.
+    // |result| is used to accumulate the result text.
+    if (index < this.declarations.length) {
+      this.decl = this.declarations[index];
+      this.result = this.inputString.substring(0, this.decl.offsets[0]);
+    } else {
+      this.decl = null;
+      this.result = this.inputString;
+    }
+  },
+
+  /**
+   * A helper function to compute the indentation of some text.  This
+   * examines the rule's existing text to guess the indentation to use;
+   * unlike |getDefaultIndentation|, which examines the entire style
+   * sheet.
+   *
+   * @param {String} string the input text
+   * @param {Number} offset the offset at which to compute the indentation
+   * @return {String} the indentation at the indicated position
+   */
+  getIndentation: function (string, offset) {
+    let originalOffset = offset;
+    for (--offset; offset >= 0; --offset) {
+      let c = string[offset];
+      if (c === "\r" || c === "\n" || c === "\f") {
+        return string.substring(offset + 1, originalOffset);
+      }
+      if (c !== " " && c !== "\t") {
+        // Found some non-whitespace character before we found a newline
+        // -- let's reset the starting point and keep going, as we saw
+        // something on the line before the declaration.
+        originalOffset = offset;
+      }
+    }
+    // Ran off the end.
+    return "";
+  },
+
+  /**
+   * Modify a property value to ensure it is "lexically safe" for
+   * insertion into a style sheet.  This function doesn't attempt to
+   * ensure that the resulting text is a valid value for the given
+   * property; but rather just that inserting the text into the style
+   * sheet will not cause unwanted changes to other rules or
+   * declarations.
+   *
+   * @param {String} text The input text.  This should include the trailing ";".
+   * @return {Array} An array of the form [anySanitized, text], where
+   *                 |anySanitized| is a boolean that indicates
+   *                  whether anything substantive has changed; and
+   *                  where |text| is the text that has been rewritten
+   *                  to be "lexically safe".
+   */
+  sanitizePropertyValue: function (text) {
+    let lexer = getCSSLexer(text);
+
+    let result = "";
+    let previousOffset = 0;
+    let braceDepth = 0;
+    let anySanitized = false;
+    while (true) {
+      let token = lexer.nextToken();
+      if (!token) {
+        break;
+      }
+
+      if (token.tokenType === "symbol") {
+        switch (token.text) {
+          case ";":
+            // We simply drop the ";" here.  This lets us cope with
+            // declarations that don't have a ";" and also other
+            // termination.  The caller handles adding the ";" again.
+            result += text.substring(previousOffset, token.startOffset);
+            previousOffset = token.endOffset;
+            break;
+
+          case "{":
+            ++braceDepth;
+            break;
+
+          case "}":
+            --braceDepth;
+            if (braceDepth < 0) {
+              // Found an unmatched close bracket.
+              braceDepth = 0;
+              // Copy out text from |previousOffset|.
+              result += text.substring(previousOffset, token.startOffset);
+              // Quote the offending symbol.
+              result += "\\" + token.text;
+              previousOffset = token.endOffset;
+              anySanitized = true;
+            }
+            break;
+        }
+      }
+    }
+
+    // Copy out any remaining text, then any needed terminators.
+    result += text.substring(previousOffset, text.length);
+    let eofFixup = lexer.performEOFFixup("", true);
+    if (eofFixup) {
+      anySanitized = true;
+      result += eofFixup;
+    }
+    return [anySanitized, result];
+  },
+
+  /**
+   * Start at |index| and skip whitespace
+   * backward in |string|.  Return the index of the first
+   * non-whitespace character, or -1 if the entire string was
+   * whitespace.
+   * @param {String} string the input string
+   * @param {Number} index the index at which to start
+   * @return {Number} index of the first non-whitespace character, or -1
+   */
+  skipWhitespaceBackward: function (string, index) {
+    for (--index;
+         index >= 0 && (string[index] === " " || string[index] === "\t");
+         --index) {
+      // Nothing.
+    }
+    return index;
+  },
+
+  /**
+   * Terminate a given declaration, if needed.
+   *
+   * @param {Number} index The index of the rule to possibly
+   *                       terminate.  It might be invalid, so this
+   *                       function must check for that.
+   */
+  maybeTerminateDecl: function (index) {
+    if (index < 0 || index >= this.declarations.length
+        // No need to rewrite declarations in comments.
+        || ("commentOffsets" in this.declarations[index])) {
+      return;
+    }
+
+    let termDecl = this.declarations[index];
+    let endIndex = termDecl.offsets[1];
+    // Due to an oddity of the lexer, we might have gotten a bit of
+    // extra whitespace in a trailing bad_url token -- so be sure to
+    // skip that as well.
+    endIndex = this.skipWhitespaceBackward(this.result, endIndex) + 1;
+
+    let trailingText = this.result.substring(endIndex);
+    if (termDecl.terminator) {
+      // Insert the terminator just at the end of the declaration,
+      // before any trailing whitespace.
+      this.result = this.result.substring(0, endIndex) + termDecl.terminator +
+        trailingText;
+      // In a couple of cases, we may have had to add something to
+      // terminate the declaration, but the termination did not
+      // actually affect the property's value -- and at this spot, we
+      // only care about reporting value changes.  In particular, we
+      // might have added a plain ";", or we might have terminated a
+      // comment with "*/;".  Neither of these affect the value.
+      if (termDecl.terminator !== ";" && termDecl.terminator !== "*/;") {
+        this.changedDeclarations[index] =
+          termDecl.value + termDecl.terminator.slice(0, -1);
+      }
+    }
+    // If the rule generally has newlines, but this particular
+    // declaration doesn't have a trailing newline, insert one now.
+    // Maybe this style is too weird to bother with.
+    if (this.hasNewLine && !NEWLINE_RX.test(trailingText)) {
+      this.result += "\n";
+    }
+  },
+
+  /**
+   * Sanitize the given property value and return the sanitized form.
+   * If the property is rewritten during sanitization, make a note in
+   * |changedDeclarations|.
+   *
+   * @param {String} text The property text.
+   * @param {Number} index The index of the property.
+   * @return {String} The sanitized text.
+   */
+  sanitizeText: function (text, index) {
+    let [anySanitized, sanitizedText] = this.sanitizePropertyValue(text);
+    if (anySanitized) {
+      this.changedDeclarations[index] = sanitizedText;
+    }
+    return sanitizedText;
+  },
+
+  /**
+   * Rename a declaration.
+   *
+   * @param {Number} index index of the property in the rule.
+   * @param {String} name current name of the property
+   * @param {String} newName new name of the property
+   */
+  renameProperty: function (index, name, newName) {
+    this.completeInitialization(index);
+    this.result += CSS.escape(newName);
+    // We could conceivably compute the name offsets instead so we
+    // could preserve white space and comments on the LHS of the ":".
+    this.completeCopying(this.decl.colonOffsets[0]);
+  },
+
+  /**
+   * Enable or disable a declaration
+   *
+   * @param {Number} index index of the property in the rule.
+   * @param {String} name current name of the property
+   * @param {Boolean} isEnabled true if the property should be enabled;
+   *                        false if it should be disabled
+   */
+  setPropertyEnabled: function (index, name, isEnabled) {
+    this.completeInitialization(index);
+    const decl = this.decl;
+    let copyOffset = decl.offsets[1];
+    if (isEnabled) {
+      // Enable it.  First see if the comment start can be deleted.
+      let commentStart = decl.commentOffsets[0];
+      if (EMPTY_COMMENT_START_RX.test(this.result.substring(commentStart))) {
+        this.result = this.result.substring(0, commentStart);
+      } else {
+        this.result += "*/ ";
+      }
+
+      // Insert the name and value separately, so we can report
+      // sanitization changes properly.
+      let commentNamePart =
+          this.inputString.substring(decl.offsets[0],
+                                     decl.colonOffsets[1]);
+      this.result += unescapeCSSComment(commentNamePart);
+
+      // When uncommenting, we must be sure to sanitize the text, to
+      // avoid things like /* decl: }; */, which will be accepted as
+      // a property but which would break the entire style sheet.
+      let newText = this.inputString.substring(decl.colonOffsets[1],
+                                               decl.offsets[1]);
+      newText = unescapeCSSComment(newText).trimRight();
+      this.result += this.sanitizeText(newText, index) + ";";
+
+      // See if the comment end can be deleted.
+      let trailingText = this.inputString.substring(decl.offsets[1]);
+      if (EMPTY_COMMENT_END_RX.test(trailingText)) {
+        copyOffset = decl.commentOffsets[1];
+      } else {
+        this.result += " /*";
+      }
+    } else {
+      // Disable it.  Note that we use our special comment syntax
+      // here.
+      let declText = this.inputString.substring(decl.offsets[0],
+                                                decl.offsets[1]);
+      this.result += "/*" + COMMENT_PARSING_HEURISTIC_BYPASS_CHAR +
+        " " + escapeCSSComment(declText) + " */";
+    }
+    this.completeCopying(copyOffset);
+  },
+
+  /**
+   * Return a promise that will be resolved to the default indentation
+   * of the rule.  This is a helper for internalCreateProperty.
+   *
+   * @return {Promise} a promise that will be resolved to a string
+   *         that holds the default indentation that should be used
+   *         for edits to the rule.
+   */
+  getDefaultIndentation: function () {
+    return this.rule.parentStyleSheet.guessIndentation();
+  },
+
+  /**
+   * An internal function to create a new declaration.  This does all
+   * the work of |createProperty|.
+   *
+   * @param {Number} index index of the property in the rule.
+   * @param {String} name name of the new property
+   * @param {String} value value of the new property
+   * @param {String} priority priority of the new property; either
+   *                          the empty string or "important"
+   * @return {Promise} a promise that is resolved when the edit has
+   *                   completed
+   */
+  internalCreateProperty: Task.async(function* (index, name, value, priority) {
+    this.completeInitialization(index);
+    let newIndentation = "";
+    if (this.hasNewLine) {
+      if (this.declarations.length > 0) {
+        newIndentation = this.getIndentation(this.inputString,
+                                             this.declarations[0].offsets[0]);
+      } else if (this.defaultIndentation) {
+        newIndentation = this.defaultIndentation;
+      } else {
+        newIndentation = yield this.getDefaultIndentation();
+      }
+    }
+
+    this.maybeTerminateDecl(index - 1);
+
+    // If we generally have newlines, and if skipping whitespace
+    // backward stops at a newline, then insert our text before that
+    // whitespace.  This ensures the indentation we computed is what
+    // is actually used.
+    let savedWhitespace = "";
+    if (this.hasNewLine) {
+      let wsOffset = this.skipWhitespaceBackward(this.result,
+                                                 this.result.length);
+      if (this.result[wsOffset] === "\r" || this.result[wsOffset] === "\n") {
+        savedWhitespace = this.result.substring(wsOffset + 1);
+        this.result = this.result.substring(0, wsOffset + 1);
+      }
+    }
+
+    this.result += newIndentation + CSS.escape(name) + ": " +
+      this.sanitizeText(value, index);
+
+    if (priority === "important") {
+      this.result += " !important";
+    }
+    this.result += ";";
+    if (this.hasNewLine) {
+      this.result += "\n";
+    }
+    this.result += savedWhitespace;
+
+    if (this.decl) {
+      // Still want to copy in the declaration previously at this
+      // index.
+      this.completeCopying(this.decl.offsets[0]);
+    }
+  }),
+
+  /**
+   * Create a new declaration.
+   *
+   * @param {Number} index index of the property in the rule.
+   * @param {String} name name of the new property
+   * @param {String} value value of the new property
+   * @param {String} priority priority of the new property; either
+   *                          the empty string or "important"
+   */
+  createProperty: function (index, name, value, priority) {
+    this.editPromise = this.internalCreateProperty(index, name, value,
+                                                   priority);
+  },
+
+  /**
+   * Set a declaration's value.
+   *
+   * @param {Number} index index of the property in the rule.
+   *                       This can be -1 in the case where
+   *                       the rule does not support setRuleText;
+   *                       generally for setting properties
+   *                       on an element's style.
+   * @param {String} name the property's name
+   * @param {String} value the property's value
+   * @param {String} priority the property's priority, either the empty
+   *                          string or "important"
+   */
+  setProperty: function (index, name, value, priority) {
+    this.completeInitialization(index);
+    // We might see a "set" on a previously non-existent property; in
+    // that case, act like "create".
+    if (!this.decl) {
+      this.createProperty(index, name, value, priority);
+      return;
+    }
+
+    // Note that this assumes that "set" never operates on disabled
+    // properties.
+    this.result += this.inputString.substring(this.decl.offsets[0],
+                                              this.decl.colonOffsets[1]) +
+      this.sanitizeText(value, index);
+
+    if (priority === "important") {
+      this.result += " !important";
+    }
+    this.result += ";";
+    this.completeCopying(this.decl.offsets[1]);
+  },
+
+  /**
+   * Remove a declaration.
+   *
+   * @param {Number} index index of the property in the rule.
+   * @param {String} name the name of the property to remove
+   */
+  removeProperty: function (index, name) {
+    this.completeInitialization(index);
+
+    // If asked to remove a property that does not exist, bail out.
+    if (!this.decl) {
+      return;
+    }
+
+    let copyOffset = this.decl.offsets[1];
+    // Maybe removing this rule left us with a completely blank
+    // line.  In this case, we'll delete the whole thing.  We only
+    // bother with this if we're looking at sources that already
+    // have a newline somewhere.
+    if (this.hasNewLine) {
+      let nlOffset = this.skipWhitespaceBackward(this.result,
+                                                 this.decl.offsets[0]);
+      if (nlOffset < 0 || this.result[nlOffset] === "\r" ||
+          this.result[nlOffset] === "\n") {
+        let trailingText = this.inputString.substring(copyOffset);
+        let match = BLANK_LINE_RX.exec(trailingText);
+        if (match) {
+          this.result = this.result.substring(0, nlOffset + 1);
+          copyOffset += match[0].length;
+        }
+      }
+    }
+    this.completeCopying(copyOffset);
+  },
+
+  /**
+   * An internal function to copy any trailing text to the output
+   * string.
+   *
+   * @param {Number} copyOffset Offset into |inputString| of the
+   *        final text to copy to the output string.
+   */
+  completeCopying: function (copyOffset) {
+    // Add the trailing text.
+    this.result += this.inputString.substring(copyOffset);
+  },
+
+  /**
+   * Apply the modifications in this object to the associated rule.
+   *
+   * @return {Promise} A promise which will be resolved when the modifications
+   *         are complete.
+   */
+  apply: function () {
+    return promise.resolve(this.editPromise).then(() => {
+      return this.rule.setRuleText(this.result);
+    });
+  },
+
+  /**
+   * Get the result of the rewriting.  This is used for testing.
+   *
+   * @return {object} an object of the form {changed: object, text: string}
+   *                  |changed| is an object where each key is
+   *                  the index of a property whose value had to be
+   *                  rewritten during the sanitization process, and
+   *                  whose value is the new text of the property.
+   *                  |text| is the rewritten text of the rule.
+   */
+  getResult: function () {
+    return {changed: this.changedDeclarations, text: this.result};
+  },
+};
+
+/**
+ * Returns an array of the parsed CSS selector value and type given a string.
+ *
+ * The components making up the CSS selector can be extracted into 3 different
+ * types: element, attribute and pseudoclass. The object that is appended to
+ * the returned array contains the value related to one of the 3 types described
+ * along with the actual type.
+ *
+ * The following are the 3 types that can be returned in the object signature:
+ * (1) SELECTOR_ATTRIBUTE
+ * (2) SELECTOR_ELEMENT
+ * (3) SELECTOR_PSEUDO_CLASS
+ *
+ * @param {String} value
+ *        The CSS selector text.
+ * @return {Array} an array of objects with the following signature:
+ *         [{ "value": string, "type": integer }, ...]
+ */
+function parsePseudoClassesAndAttributes(value) {
+  if (!value) {
+    throw new Error("empty input string");
+  }
+
+  let tokens = cssTokenizer(value);
+  let result = [];
+  let current = "";
+  let functionCount = 0;
+  let hasAttribute = false;
+  let hasColon = false;
+
+  for (let token of tokens) {
+    if (token.tokenType === "ident") {
+      current += value.substring(token.startOffset, token.endOffset);
+
+      if (hasColon && !functionCount) {
+        if (current) {
+          result.push({ value: current, type: SELECTOR_PSEUDO_CLASS });
+        }
+
+        current = "";
+        hasColon = false;
+      }
+    } else if (token.tokenType === "symbol" && token.text === ":") {
+      if (!hasColon) {
+        if (current) {
+          result.push({ value: current, type: SELECTOR_ELEMENT });
+        }
+
+        current = "";
+        hasColon = true;
+      }
+
+      current += token.text;
+    } else if (token.tokenType === "function") {
+      current += value.substring(token.startOffset, token.endOffset);
+      functionCount++;
+    } else if (token.tokenType === "symbol" && token.text === ")") {
+      current += token.text;
+
+      if (hasColon && functionCount == 1) {
+        if (current) {
+          result.push({ value: current, type: SELECTOR_PSEUDO_CLASS });
+        }
+
+        current = "";
+        functionCount--;
+        hasColon = false;
+      } else {
+        functionCount--;
+      }
+    } else if (token.tokenType === "symbol" && token.text === "[") {
+      if (!hasAttribute && !functionCount) {
+        if (current) {
+          result.push({ value: current, type: SELECTOR_ELEMENT });
+        }
+
+        current = "";
+        hasAttribute = true;
+      }
+
+      current += token.text;
+    } else if (token.tokenType === "symbol" && token.text === "]") {
+      current += token.text;
+
+      if (hasAttribute && !functionCount) {
+        if (current) {
+          result.push({ value: current, type: SELECTOR_ATTRIBUTE });
+        }
+
+        current = "";
+        hasAttribute = false;
+      }
+    } else {
+      current += value.substring(token.startOffset, token.endOffset);
+    }
+  }
+
+  if (current) {
+    result.push({ value: current, type: SELECTOR_ELEMENT });
+  }
+
+  return result;
+}
+
+/**
+ * Expects a single CSS value to be passed as the input and parses the value
+ * and priority.
+ *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server.
+ * @param {String} value
+ *        The value from the text editor.
+ * @return {Object} an object with 'value' and 'priority' properties.
+ */
+function parseSingleValue(isCssPropertyKnown, value) {
+  let declaration = parseDeclarations(isCssPropertyKnown,
+                                      "a: " + value + ";")[0];
+  return {
+    value: declaration ? declaration.value : "",
+    priority: declaration ? declaration.priority : ""
+  };
+}
+
+exports.cssTokenizer = cssTokenizer;
+exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
+exports.escapeCSSComment = escapeCSSComment;
+// unescapeCSSComment is exported for testing.
+exports._unescapeCSSComment = unescapeCSSComment;
+exports.parseDeclarations = parseDeclarations;
+// parseCommentDeclarations is exported for testing.
+exports._parseCommentDeclarations = parseCommentDeclarations;
+exports.RuleRewriter = RuleRewriter;
+exports.parsePseudoClassesAndAttributes = parsePseudoClassesAndAttributes;
+exports.parseSingleValue = parseSingleValue;
rename from devtools/shared/css-properties-db.js
rename to devtools/shared/css/properties-db.js
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -1,18 +1,18 @@
 /* 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 { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
 const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
 const { Task } = require("devtools/shared/task");
-const { CSS_PROPERTIES_DB } = require("devtools/shared/css-properties-db");
-const { cssColors } = require("devtools/shared/css-color-db");
+const { CSS_PROPERTIES_DB } = require("devtools/shared/css/properties-db");
+const { cssColors } = require("devtools/shared/css/color-db");
 
 /**
  * Build up a regular expression that matches a CSS variable token. This is an
  * ident token that starts with two dashes "--".
  *
  * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
  */
 var NON_ASCII = "[^\\x00-\\x7F]";
@@ -86,17 +86,17 @@ CssProperties.prototype = {
    * @return {Boolean}
    */
   isInherited(property) {
     return this.properties[property] && this.properties[property].isInherited;
   },
 
   /**
    * Checks if the property supports the given CSS type.
-   * CSS types should come from devtools/shared/css-properties-db.js' CSS_TYPES.
+   * CSS types should come from devtools/shared/css/properties-db.js' CSS_TYPES.
    *
    * @param {String} property The property to be checked.
    * @param {Number} type One of the type values from CSS_TYPES.
    * @return {Boolean}
    */
   supportsType(property, type) {
     return this.properties[property] && this.properties[property].supports.includes(type);
   },
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -12,17 +12,17 @@ const {
 } = require("devtools/shared/protocol");
 const {
   pageStyleSpec,
   styleRuleSpec
 } = require("devtools/shared/specs/styles");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const { Class } = require("sdk/core/heritage");
-const { RuleRewriter } = require("devtools/shared/css-parsing-utils");
+const { RuleRewriter } = require("devtools/shared/css/parsing-utils");
 
 /**
  * PageStyleFront, the front object for the PageStyleActor
  */
 const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
   initialize: function (conn, form, ctx, detail) {
     Front.prototype.initialize.call(this, conn, form, ctx, detail);
     this.inspector = this.parent();
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -36,17 +36,17 @@
  * styling information in the page, and present this to the user in a way that
  * helps them understand:
  * - why their expectations may not have been fulfilled
  * - how browsers process CSS
  * @constructor
  */
 
 const Services = require("Services");
-const CSSLexer = require("devtools/shared/css-lexer");
+const CSSLexer = require("devtools/shared/css/lexer");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const styleInspectorL10N =
   new LocalizationHelper("devtools-shared/locale/styleinspector.properties");
 
 /**
  * Special values for filter, in addition to an href these values can be used
  */
 exports.FILTER = {
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include('../templates.mozbuild')
 
 DIRS += [
     'acorn',
     'apps',
     'client',
+    'css',
     'discovery',
     'fronts',
     'gcli',
     'heapsnapshot',
     'inspector',
     'jsbeautify',
     'layout',
     'locales',
@@ -40,21 +41,16 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'async-storage.js',
     'async-utils.js',
     'builtin-modules.js',
     'content-observer.js',
-    'css-color-db.js',
-    'css-color.js',
-    'css-lexer.js',
-    'css-parsing-utils.js',
-    'css-properties-db.js',
     'defer.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'dom-node-constants.js',
     'dom-node-filter-constants.js',
     'event-emitter.js',
     'flags.js',
     'indentation.js',
--- a/devtools/shared/tests/unit/test_css-properties-db.js
+++ b/devtools/shared/tests/unit/test_css-properties-db.js
@@ -4,31 +4,31 @@
 // Test that the devtool's client-side css-properties-db matches the values on the
 // platform.
 
 "use strict";
 
 const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
                            .getService(Components.interfaces.inIDOMUtils);
 
-const {PSEUDO_ELEMENTS, CSS_PROPERTIES} = require("devtools/shared/css-properties-db");
+const {PSEUDO_ELEMENTS, CSS_PROPERTIES} = require("devtools/shared/css/properties-db");
 const {generateCssProperties} = require("devtools/server/actors/css-properties");
 
 function run_test() {
   // Check that the platform and client match for pseudo elements.
   deepEqual(PSEUDO_ELEMENTS, DOMUtils.getCSSPseudoElementNames(),
             "If this assertion fails, then the client side CSS pseudo elements list in " +
             "devtools is out of date with the pseudo elements on the platform. To fix " +
-            "this assertion open devtools/shared/css-properties-db.js and follow the " +
+            "this assertion open devtools/shared/css/properties-db.js and follow the " +
             "instructions above the CSS_PSEUDO_ELEMENTS on how to re-generate the list.");
 
   const propertiesErrorMessage = "If this assertion fails, then the client side CSS " +
                                  "properties list in devtools is out of date with the " +
                                  "CSS properties on the platform. To fix this " +
-                                 "assertion open devtools/shared/css-properties-db.js " +
+                                 "assertion open devtools/shared/css/properties-db.js " +
                                  "and follow the instructions above the CSS_PROPERTIES " +
                                  "on how to re-generate the list.";
 
   // Check that the platform and client match for CSS properties. Enumerate each property
   // to aid in debugging.
   const platformProperties = generateCssProperties();
   for (let propertyName in CSS_PROPERTIES) {
     const platformProperty = platformProperties[propertyName];
--- a/devtools/shared/tests/unit/test_csslexer.js
+++ b/devtools/shared/tests/unit/test_csslexer.js
@@ -4,17 +4,17 @@
  */
 
 // This file is a copy of layout/style/test/test_csslexer.js, modified
 // to use both our pure-JS lexer and the DOMUtils lexer for
 // cross-checking.
 
 "use strict";
 
-const jsLexer = require("devtools/shared/css-lexer");
+const jsLexer = require("devtools/shared/css/lexer");
 const domutils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
                            .getService(Components.interfaces.inIDOMUtils);
 
 // An object that acts like a CSSLexer but verifies that the DOM lexer
 // and the JS lexer do the same thing.
 function DoubleLexer(input) {
   do_print("DoubleLexer input: " + input);
   this.domLexer = domutils.getCSSLexer(input);