new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/css-color-db.js
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// /!\ Auto-generated from nsColorNameList.h.
+// This should be kept in sync with that list.
+// test_cssColorDatabase.js tries to enforce this.
+
+const cssColors = {
+ aliceblue: [240, 248, 255, 1],
+ antiquewhite: [250, 235, 215, 1],
+ aqua: [0, 255, 255, 1],
+ aquamarine: [127, 255, 212, 1],
+ azure: [240, 255, 255, 1],
+ beige: [245, 245, 220, 1],
+ bisque: [255, 228, 196, 1],
+ black: [0, 0, 0, 1],
+ blanchedalmond: [255, 235, 205, 1],
+ blue: [0, 0, 255, 1],
+ blueviolet: [138, 43, 226, 1],
+ brown: [165, 42, 42, 1],
+ burlywood: [222, 184, 135, 1],
+ cadetblue: [95, 158, 160, 1],
+ chartreuse: [127, 255, 0, 1],
+ chocolate: [210, 105, 30, 1],
+ coral: [255, 127, 80, 1],
+ cornflowerblue: [100, 149, 237, 1],
+ cornsilk: [255, 248, 220, 1],
+ crimson: [220, 20, 60, 1],
+ cyan: [0, 255, 255, 1],
+ darkblue: [0, 0, 139, 1],
+ darkcyan: [0, 139, 139, 1],
+ darkgoldenrod: [184, 134, 11, 1],
+ darkgray: [169, 169, 169, 1],
+ darkgreen: [0, 100, 0, 1],
+ darkgrey: [169, 169, 169, 1],
+ darkkhaki: [189, 183, 107, 1],
+ darkmagenta: [139, 0, 139, 1],
+ darkolivegreen: [85, 107, 47, 1],
+ darkorange: [255, 140, 0, 1],
+ darkorchid: [153, 50, 204, 1],
+ darkred: [139, 0, 0, 1],
+ darksalmon: [233, 150, 122, 1],
+ darkseagreen: [143, 188, 143, 1],
+ darkslateblue: [72, 61, 139, 1],
+ darkslategray: [47, 79, 79, 1],
+ darkslategrey: [47, 79, 79, 1],
+ darkturquoise: [0, 206, 209, 1],
+ darkviolet: [148, 0, 211, 1],
+ deeppink: [255, 20, 147, 1],
+ deepskyblue: [0, 191, 255, 1],
+ dimgray: [105, 105, 105, 1],
+ dimgrey: [105, 105, 105, 1],
+ dodgerblue: [30, 144, 255, 1],
+ firebrick: [178, 34, 34, 1],
+ floralwhite: [255, 250, 240, 1],
+ forestgreen: [34, 139, 34, 1],
+ fuchsia: [255, 0, 255, 1],
+ gainsboro: [220, 220, 220, 1],
+ ghostwhite: [248, 248, 255, 1],
+ gold: [255, 215, 0, 1],
+ goldenrod: [218, 165, 32, 1],
+ gray: [128, 128, 128, 1],
+ grey: [128, 128, 128, 1],
+ green: [0, 128, 0, 1],
+ greenyellow: [173, 255, 47, 1],
+ honeydew: [240, 255, 240, 1],
+ hotpink: [255, 105, 180, 1],
+ indianred: [205, 92, 92, 1],
+ indigo: [75, 0, 130, 1],
+ ivory: [255, 255, 240, 1],
+ khaki: [240, 230, 140, 1],
+ lavender: [230, 230, 250, 1],
+ lavenderblush: [255, 240, 245, 1],
+ lawngreen: [124, 252, 0, 1],
+ lemonchiffon: [255, 250, 205, 1],
+ lightblue: [173, 216, 230, 1],
+ lightcoral: [240, 128, 128, 1],
+ lightcyan: [224, 255, 255, 1],
+ lightgoldenrodyellow: [250, 250, 210, 1],
+ lightgray: [211, 211, 211, 1],
+ lightgreen: [144, 238, 144, 1],
+ lightgrey: [211, 211, 211, 1],
+ lightpink: [255, 182, 193, 1],
+ lightsalmon: [255, 160, 122, 1],
+ lightseagreen: [32, 178, 170, 1],
+ lightskyblue: [135, 206, 250, 1],
+ lightslategray: [119, 136, 153, 1],
+ lightslategrey: [119, 136, 153, 1],
+ lightsteelblue: [176, 196, 222, 1],
+ lightyellow: [255, 255, 224, 1],
+ lime: [0, 255, 0, 1],
+ limegreen: [50, 205, 50, 1],
+ linen: [250, 240, 230, 1],
+ magenta: [255, 0, 255, 1],
+ maroon: [128, 0, 0, 1],
+ mediumaquamarine: [102, 205, 170, 1],
+ mediumblue: [0, 0, 205, 1],
+ mediumorchid: [186, 85, 211, 1],
+ mediumpurple: [147, 112, 219, 1],
+ mediumseagreen: [60, 179, 113, 1],
+ mediumslateblue: [123, 104, 238, 1],
+ mediumspringgreen: [0, 250, 154, 1],
+ mediumturquoise: [72, 209, 204, 1],
+ mediumvioletred: [199, 21, 133, 1],
+ midnightblue: [25, 25, 112, 1],
+ mintcream: [245, 255, 250, 1],
+ mistyrose: [255, 228, 225, 1],
+ moccasin: [255, 228, 181, 1],
+ navajowhite: [255, 222, 173, 1],
+ navy: [0, 0, 128, 1],
+ oldlace: [253, 245, 230, 1],
+ olive: [128, 128, 0, 1],
+ olivedrab: [107, 142, 35, 1],
+ orange: [255, 165, 0, 1],
+ orangered: [255, 69, 0, 1],
+ orchid: [218, 112, 214, 1],
+ palegoldenrod: [238, 232, 170, 1],
+ palegreen: [152, 251, 152, 1],
+ paleturquoise: [175, 238, 238, 1],
+ palevioletred: [219, 112, 147, 1],
+ papayawhip: [255, 239, 213, 1],
+ peachpuff: [255, 218, 185, 1],
+ peru: [205, 133, 63, 1],
+ pink: [255, 192, 203, 1],
+ plum: [221, 160, 221, 1],
+ powderblue: [176, 224, 230, 1],
+ purple: [128, 0, 128, 1],
+ rebeccapurple: [102, 51, 153, 1],
+ red: [255, 0, 0, 1],
+ rosybrown: [188, 143, 143, 1],
+ royalblue: [65, 105, 225, 1],
+ saddlebrown: [139, 69, 19, 1],
+ salmon: [250, 128, 114, 1],
+ sandybrown: [244, 164, 96, 1],
+ seagreen: [46, 139, 87, 1],
+ seashell: [255, 245, 238, 1],
+ sienna: [160, 82, 45, 1],
+ silver: [192, 192, 192, 1],
+ skyblue: [135, 206, 235, 1],
+ slateblue: [106, 90, 205, 1],
+ slategray: [112, 128, 144, 1],
+ slategrey: [112, 128, 144, 1],
+ snow: [255, 250, 250, 1],
+ springgreen: [0, 255, 127, 1],
+ steelblue: [70, 130, 180, 1],
+ tan: [210, 180, 140, 1],
+ teal: [0, 128, 128, 1],
+ thistle: [216, 191, 216, 1],
+ tomato: [255, 99, 71, 1],
+ turquoise: [64, 224, 208, 1],
+ violet: [238, 130, 238, 1],
+ wheat: [245, 222, 179, 1],
+ white: [255, 255, 255, 1],
+ whitesmoke: [245, 245, 245, 1],
+ yellow: [255, 255, 0, 1],
+ yellowgreen: [154, 205, 50, 1],
+};
+
+exports.cssColors = cssColors;
--- a/devtools/client/shared/css-color.js
+++ b/devtools/client/shared/css-color.js
@@ -2,16 +2,18 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Ci} = require("chrome");
const Services = require("Services");
+const {cssColors} = require("devtools/client/shared/css-color-db");
+
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
const SPECIALVALUES = new Set([
"currentcolor",
"initial",
"inherit",
"transparent",
"unset"
@@ -53,17 +55,20 @@ const SPECIALVALUES = new Set([
function CssColor(colorValue) {
this.newColor(colorValue);
}
module.exports.colorUtils = {
CssColor: CssColor,
rgbToHsl: rgbToHsl,
setAlpha: setAlpha,
- classifyColor: classifyColor
+ classifyColor: classifyColor,
+ rgbToColorName: rgbToColorName,
+ colorToRGBA: colorToRGBA,
+ isValidCSSColor: isValidCSSColor,
};
/**
* Values used in COLOR_UNIT_PREF
*/
CssColor.COLORUNIT = {
"authored": "authored",
"hex": "hex",
@@ -113,17 +118,17 @@ CssColor.prototype = {
get hasAlpha() {
if (!this.valid) {
return false;
}
return this._getRGBATuple().a !== 1;
},
get valid() {
- return DOMUtils.isValidCSSColor(this.authored);
+ return isValidCSSColor(this.authored);
},
/**
* Return true for all transparent values e.g. rgba(0, 0, 0, 0).
*/
get transparent() {
try {
let tuple = this._getRGBATuple();
@@ -145,17 +150,17 @@ CssColor.prototype = {
try {
let tuple = this._getRGBATuple();
if (tuple.a !== 1) {
return this.rgb;
}
let {r, g, b} = tuple;
- return DOMUtils.rgbToColorName(r, g, b);
+ return rgbToColorName(r, g, b);
} catch (e) {
return this.hex;
}
},
get hex() {
let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
if (invalidOrSpecialValue !== false) {
@@ -464,11 +469,320 @@ function classifyColor(value) {
} else if (value.startsWith("hsl(") || value.startsWith("hsla(")) {
return CssColor.COLORUNIT.hsl;
} else if (/^#[0-9a-f]+$/.exec(value)) {
return CssColor.COLORUNIT.hex;
}
return CssColor.COLORUNIT.name;
}
+// This holds a map from colors back to color names for use by
+// rgbToColorName.
+var cssRGBMap;
+
+/**
+ * Given a color, return its name, if it has one. Throws an exception
+ * if the color does not have a name.
+ *
+ * @param {Number} r, g, b The color components.
+ * @return {String} the name of the color
+ */
+function rgbToColorName(r, g, b) {
+ if (!cssRGBMap) {
+ cssRGBMap = {};
+ for (let name in cssColors) {
+ let key = JSON.stringify(cssColors[name]);
+ if (!(key in cssRGBMap)) {
+ cssRGBMap[key] = name;
+ }
+ }
+ }
+ let value = cssRGBMap[JSON.stringify([r, g, b, 1])];
+ if (!value) {
+ throw new Error("no such color");
+ }
+ return value;
+}
+
+// Originally from dom/tests/mochitest/ajax/mochikit/MochiKit/Color.js.
+function _hslValue(n1, n2, hue) {
+ if (hue > 6.0) {
+ hue -= 6.0;
+ } else if (hue < 0.0) {
+ hue += 6.0;
+ }
+ let val;
+ if (hue < 1.0) {
+ val = n1 + (n2 - n1) * hue;
+ } else if (hue < 3.0) {
+ val = n2;
+ } else if (hue < 4.0) {
+ val = n1 + (n2 - n1) * (4.0 - hue);
+ } else {
+ val = n1;
+ }
+ return val;
+}
+
+// Originally from dom/tests/mochitest/ajax/mochikit/MochiKit/Color.js.
+function hslToRGB([hue, saturation, lightness]) {
+ let red;
+ let green;
+ let blue;
+ if (saturation === 0) {
+ red = lightness;
+ green = lightness;
+ blue = lightness;
+ } else {
+ let m2;
+ if (lightness <= 0.5) {
+ m2 = lightness * (1.0 + saturation);
+ } else {
+ m2 = lightness + saturation - (lightness * saturation);
+ }
+ let m1 = (2.0 * lightness) - m2;
+ let f = _hslValue;
+ let h6 = hue * 6.0;
+ red = f(m1, m2, h6 + 2);
+ green = f(m1, m2, h6);
+ blue = f(m1, m2, h6 - 2);
+ }
+ return [red, green, blue];
+}
+
+/**
+ * A helper function to convert a hex string like "F0C" 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;
+
+ if (name.length === 3) {
+ let val = parseInt(name, 16);
+ b = ((val & 15) << 4) + (val & 15);
+ val >>= 4;
+ g = ((val & 15) << 4) + (val & 15);
+ val >>= 4;
+ r = ((val & 15) << 4) + (val & 15);
+ } else if (name.length === 6) {
+ let val = parseInt(name, 16);
+ b = val & 255;
+ val >>= 8;
+ g = val & 255;
+ val >>= 8;
+ r = val & 255;
+ } else {
+ return null;
+ }
+
+ return {r, g, b, a: 1};
+}
+
+/**
+ * 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 % 60;
+ if (val < 0) {
+ val += 60;
+ }
+ vals.push(val / 60.0);
+
+ 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, 100));
+ }
+
+ return hslToRGB(vals).map((elt) => Math.trunc(elt * 255));
+}
+
+/**
+ * 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, 100)));
+ } 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 = DOMUtils.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;
+}
+
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});