Bug 911748 - Add default color dropdown to devtools options panel. r=jwalker

This commit is contained in:
Michael Ratcliffe
2013-09-16 11:01:25 +01:00
parent 711557704c
commit 84ffe6abb6
18 changed files with 978 additions and 37 deletions

View File

@@ -1092,6 +1092,9 @@ pref("devtools.inspector.markupPreview", false);
pref("devtools.inspector.remote", false);
pref("devtools.inspector.show_pseudo_elements", true);
// DevTools default color unit
pref("devtools.defaultColorUnit", "hex");
// Enable the Responsive UI tool
pref("devtools.responsiveUI.enabled", true);
pref("devtools.responsiveUI.no-reload-notification", false);

View File

@@ -178,6 +178,27 @@ OptionsPanel.prototype = {
gDevTools.emit("pref-changed", data);
}.bind(radiogroup));
}
let prefMenulists = this.panelDoc.querySelectorAll("menulist[data-pref]");
for (let menulist of prefMenulists) {
let pref = Services.prefs.getCharPref(menulist.getAttribute("data-pref"));
let menuitems = menulist.querySelectorAll("menuitem");
for (let menuitem of menuitems) {
let value = menuitem.getAttribute("value");
if (value === pref) {
menulist.selectedItem = menuitem;
break;
}
}
menulist.addEventListener("command", function() {
let data = {
pref: this.getAttribute("data-pref"),
newValue: this.value
};
data.oldValue = Services.prefs.getCharPref(data.pref);
Services.prefs.setCharPref(data.pref, data.newValue);
gDevTools.emit("pref-changed", data);
}.bind(menulist));
}
},
/**

View File

@@ -31,6 +31,24 @@
<radio value="light" label="&options.lightTheme.label;"/>
<radio value="dark" label="&options.darkTheme.label;"/>
</radiogroup>
<label value="&options.context.inspector;"/>
<vbox id="inspector-options" class="options-groupbox">
<hbox align="center">
<label value="&options.defaultColorUnit.label;"
control="defaultColorUnitMenuList"
accesskey="&options.defaultColorUnit.accesskey;"/>
<menulist id="defaultColorUnitMenuList"
label="&options.defaultColorUnit.label;"
data-pref="devtools.defaultColorUnit">
<menupopup>
<menuitem label="&options.defaultColorUnit.hex;" value="hex"/>
<menuitem label="&options.defaultColorUnit.hsl;" value="hsl"/>
<menuitem label="&options.defaultColorUnit.rgb;" value="rgb"/>
<menuitem label="&options.defaultColorUnit.name;" value="name"/>
</menupopup>
</menulist>
</hbox>
</vbox>
<label value="&options.webconsole.label;"/>
<vbox id="webconsole-options" class="options-groupbox">
<checkbox label="&options.enablePersistentLogging.label;"

View File

@@ -16,6 +16,8 @@ const COLLAPSE_DATA_URL_LENGTH = 60;
const {UndoStack} = require("devtools/shared/undo");
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const {colorUtils} = require("devtools/shared/css-color");
const promise = require("sdk/core/promise");
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
@@ -75,7 +77,7 @@ function MarkupView(aInspector, aFrame, aControllerWindow)
this._containers = new WeakMap();
this._boundMutationObserver = this._mutationObserver.bind(this);
this.walker.on("mutations", this._boundMutationObserver)
this.walker.on("mutations", this._boundMutationObserver);
this._boundOnNewSelection = this._onNewSelection.bind(this);
this._inspector.selection.on("new-node-front", this._boundOnNewSelection);
@@ -87,6 +89,9 @@ function MarkupView(aInspector, aFrame, aControllerWindow)
this._boundFocus = this._onFocus.bind(this);
this._frame.addEventListener("focus", this._boundFocus, false);
this._handlePrefChange = this._handlePrefChange.bind(this);
gDevTools.on("pref-changed", this._handlePrefChange);
this._initPreview();
}
@@ -112,6 +117,33 @@ MarkupView.prototype = {
return this._containers.get(aNode);
},
_handlePrefChange: function(event, data) {
if (data.pref == "devtools.defaultColorUnit") {
this.update();
}
},
update: function() {
let updateChildren = function(node) {
this.getContainer(node).update();
for (let child of node.treeChildren()) {
updateChildren(child);
}
}.bind(this);
// Start with the documentElement
let documentElement;
for (let node of this._rootNode.treeChildren()) {
if (node.isDocumentElement === true) {
documentElement = node;
break;
}
}
// Recursively update each node starting with documentElement.
updateChildren(documentElement);
},
/**
* Highlight the inspector selected node.
*/
@@ -380,7 +412,7 @@ MarkupView.prototype = {
continue;
}
if (type === "attributes" || type === "characterData") {
container.update();
container.update(false);
} else if (type === "childList") {
container.childrenDirty = true;
this._updateChildren(container);
@@ -697,6 +729,8 @@ MarkupView.prototype = {
*/
destroy: function MT_destroy()
{
gDevTools.off("pref-changed", this._handlePrefChange);
this.undo.destroy();
delete this.undo;
@@ -1020,9 +1054,9 @@ MarkupContainer.prototype = {
* Update the container's editor to the current state of the
* viewed node.
*/
update: function() {
update: function(parseColors=true) {
if (this.editor.update) {
this.editor.update();
this.editor.update(parseColors);
}
},
@@ -1241,7 +1275,7 @@ ElementEditor.prototype = {
/**
* Update the state of the editor from the node.
*/
update: function EE_update()
update: function EE_update(parseColors=true)
{
let attrs = this.node.attributes;
if (!attrs) {
@@ -1260,10 +1294,13 @@ ElementEditor.prototype = {
// Get the attribute editor for each attribute that exists on
// the node and show it.
for (let i = 0; i < attrs.length; i++) {
let attr = this._createAttribute(attrs[i]);
if (!attr.inplaceEditor) {
attr.style.removeProperty("display");
for (let attr of attrs) {
if (parseColors && typeof attr.value !== "undefined") {
attr.value = colorUtils.processCSSString(attr.value);
}
let attribute = this._createAttribute(attr);
if (!attribute.inplaceEditor) {
attribute.style.removeProperty("display");
}
}
},

View File

@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MOCHITEST_BROWSER_FILES := \
browser_inspector_markup_colorconversion.js \
browser_inspector_markup_navigation.html \
browser_inspector_markup_navigation.js \
browser_inspector_markup_mutation.html \

View File

@@ -0,0 +1,90 @@
/* Any copyright", " is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
let inspector;
let doc;
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,browser_inspector_markupview_colorconversion.js";
function createDocument() {
doc.body.innerHTML = '' +
'<span style="color:red; border-radius:10px; ' +
'background-color:rgba(0, 255, 0, 1); display: inline-block;">' +
'Some styled text</span>';
doc.title = "Style Inspector key binding test";
setupTest();
}
function setupTest() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
inspector = toolbox.getCurrentPanel();
inspector.once("inspector-updated", checkColors);
});
}
function checkColors() {
let node = content.document.querySelector("span");
assertAttributes(node, {
style: "color:#F00; border-radius:10px; background-color:#0F0; display: inline-block;"
}).then(() => {
finishUp();
});
}
// This version of assertAttributes is different from that in other markup
// view tests. This is because in most tests we are checking the node
// attributes but here we need the actual values displayed in the markup panel.
function assertAttributes(node, attributes) {
let deferred = promise.defer();
let attrsToCheck = Object.getOwnPropertyNames(attributes);
ok(node, "captain, we have the node");
let checkAttrs = function() {
let container = inspector.markup._selectedContainer;
let nodeAttrs = container.editor.attrs;
is(node.attributes.length, attrsToCheck.length,
"Node has the correct number of attributes");
for (let attr of attrsToCheck) {
ok(nodeAttrs[attr], "Node has a " + attr + "attribute");
let nodeAttributeText = nodeAttrs[attr].textContent;
[, nodeAttributeText] = nodeAttributeText.match(/^\s*[\w-]+\s*=\s*"(.*)"$/);
is(nodeAttributeText, attributes[attr],
"Node has the correct " + attr + " attribute value.");
}
deferred.resolve();
};
if (inspector.selection.node == node) {
checkAttrs();
} else {
inspector.once("inspector-updated", () => {
checkAttrs();
});
inspector.selection.setNode(node);
}
return deferred.promise;
}
function finishUp() {
doc = inspector = null;
gBrowser.removeCurrentTab();
finish();
}
}

View File

@@ -0,0 +1,398 @@
/* 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 COLOR_UNIT_PREF = "devtools.defaultColorUnit";
const {Cc, Ci, Cu} = require("chrome");
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
/**
* This module is used to convert between various color types.
*
* Usage:
* let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
* let {colorUtils} = devtools.require("devtools/shared/css-color");
*
* color.authored === "red"
* color.hasAlpha === false
* color.valid === true
* color.transparent === false // transparent has a special status.
* color.name === "red" // returns hex or rgba when no name available.
* color.hex === "#F00" // returns shortHex when available else returns
* longHex. If alpha channel is present then we
* return this.rgba.
* color.longHex === "#FF0000" // If alpha channel is present then we return
* this.rgba.
* color.rgb === "rgb(255, 0, 0)" // If alpha channel is present then we return
* this.rgba.
* color.rgba === "rgba(255, 0, 0, 1)"
* color.hsl === "hsl(0, 100%, 50%)"
* color.hsla === "hsla(0, 100%, 50%, 1)" // If alpha channel is present
* then we return this.rgba.
*
* color.toString() === "#F00"; // Outputs the color type determined in the
* COLOR_UNIT_PREF constant (above).
* // Color objects can be reused
* color.newColor("green") === "#0F0"; // true
*
* let processed = colorUtils.processCSSString("color:red; background-color:green;");
* // Returns "color:#F00; background-color:#0F0;"
*
* Valid values for COLOR_UNIT_PREF are contained in CssColor.COLORUNIT.
*/
function CssColor(colorValue) {
this.newColor(colorValue);
}
module.exports.colorUtils = {
CssColor: CssColor,
processCSSString: processCSSString
};
/**
* Values used in COLOR_UNIT_PREF
*/
CssColor.COLORUNIT = {
"authored": "authored",
"hex": "hex",
"name": "name",
"rgb": "rgb",
"hsl": "hsl"
};
CssColor.prototype = {
authored: null,
get hasAlpha() {
if (!this.valid || this.transparent) {
return false;
}
return this._getRGBATuple().a !== 1;
},
get valid() {
return this._validateColor(this.authored);
},
get transparent() {
try {
let tuple = this._getRGBATuple();
return tuple === "transparent";
} catch(e) {
return false;
}
},
get name() {
if (!this.valid) {
return "";
}
if (this.authored === "transparent") {
return "transparent";
}
try {
let tuple = this._getRGBATuple();
if (tuple === "transparent") {
return "transparent";
}
if (tuple.a !== 1) {
return this.rgb;
}
let {r, g, b} = tuple;
return DOMUtils.rgbToColorName(r, g, b);
} catch(e) {
return this.hex;
}
},
get hex() {
if (!this.valid) {
return "";
}
if (this.hasAlpha) {
return this.rgba;
}
if (this.transparent) {
return "transparent";
}
let hex = this.longHex;
if (hex.charAt(1) == hex.charAt(2) &&
hex.charAt(3) == hex.charAt(4) &&
hex.charAt(5) == hex.charAt(6)) {
hex = "#" + hex.charAt(1) + hex.charAt(3) + hex.charAt(5);
}
return hex;
},
get longHex() {
if (!this.valid) {
return "";
}
if (this.hasAlpha) {
return this.rgba;
}
if (this.transparent) {
return "transparent";
}
return this.rgb.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, function(_, r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase();
});
},
get rgb() {
if (!this.valid) {
return "";
}
if (this.transparent) {
return "transparent";
}
if (!this.hasAlpha) {
let tuple = this._getRGBATuple();
return "rgb(" + tuple.r + ", " + tuple.g + ", " + tuple.b + ")";
}
return this.rgba;
},
get rgba() {
if (!this.valid) {
return "";
}
if (this.transparent) {
return "transparent";
}
let components = this._getRGBATuple();
return "rgba(" + components.r + ", " +
components.g + ", " +
components.b + ", " +
components.a + ")";
},
get hsl() {
if (!this.valid) {
return "";
}
if (this.transparent) {
return "transparent";
}
if (this.hasAlpha) {
return this.hsla;
}
return this._hslNoAlpha();
},
get hsla() {
if (!this.valid) {
return "";
}
if (this.transparent) {
return "transparent";
}
// Because an hsla rbg roundtrip would lose accuracy we use the authored
// values if this is an hsla color.
if (this.authored.startsWith("hsla(")) {
let [, h, s, l, a] = /^\bhsla\(([\d.]+),\s*([\d.]+%),\s*([\d.]+%),\s*([\d.]+|0|1)\)$/gi.exec(this.authored);
return "hsla(" + h + ", " + s + ", " + l + ", " + a + ")";
}
if (this.hasAlpha) {
let a = this._getRGBATuple().a;
return this._hslNoAlpha().replace("hsl", "hsla").replace(")", ", " + a + ")");
}
return this._hslNoAlpha().replace("hsl", "hsla").replace(")", ", 1)");
},
/**
* Change color
*
* @param {String} color
* Any valid color string
*/
newColor: function(color) {
this.authored = color.toLowerCase();
return this;
},
/**
* Return a string representing a color of type defined in COLOR_UNIT_PREF.
*/
toString: function() {
let color;
let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
let unit = CssColor.COLORUNIT[defaultUnit];
switch(unit) {
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;
}
return color;
},
/**
* Returns a RGBA 4-Tuple representation of a color or transparent as
* appropriate.
*/
_getRGBATuple: function() {
let win = Services.appShell.hiddenDOMWindow;
let doc = win.document;
let span = doc.createElement("span");
span.style.color = this.authored;
let computed = win.getComputedStyle(span).color;
if (computed === "transparent") {
return "transparent";
}
let rgba = /^rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+\.\d+|1|0)\)$/gi.exec(computed);
if (rgba) {
let [, r, g, b, a] = rgba;
return {r: r, g: g, b: b, a: a};
} else {
let rgb = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/gi.exec(computed);
let [, r, g, b] = rgb;
return {r: r, g: g, b: b, a: 1};
}
},
_hslNoAlpha: function() {
let {r, g, b} = this._getRGBATuple();
// Because an hsl rbg roundtrip would lose accuracy we use the authored
// values if this is an hsla color.
if (this.authored.startsWith("hsl(")) {
let [, h, s, l] = /^\bhsl\(([\d.]+),\s*([\d.]+%),\s*([\d.]+%)\)$/gi.exec(this.authored);
return "hsl(" + h + ", " + s + ", " + l + ")";
}
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 "hsl(" + (Math.round(h * 1000)) / 1000 +
", " + Math.round(s * 100) +
"%, " + Math.round(l * 100) + "%)";
},
/**
* This method allows comparison of CssColor objects using ===.
*/
valueOf: function() {
return this.rgba;
},
_validateColor: function(color) {
if (typeof color !== "string" || color === "") {
return false;
}
let win = Services.appShell.hiddenDOMWindow;
let doc = win.document;
// Create a black span in a hidden window.
let span = doc.createElement("span");
span.style.color = "rgb(0, 0, 0)";
// Attempt to set the color. If the color is no longer black we know that
// color is valid.
span.style.color = color;
if (span.style.color !== "rgb(0, 0, 0)") {
return true;
}
// If the color is black then the above check will have failed. We change
// the span to white and attempt to reapply the color. If the span is not
// white then we know that the color is valid otherwise we return invalid.
span.style.color = "rgb(255, 255, 255)";
span.style.color = color;
return span.style.color !== "rgb(255, 255, 255)";
},
};
/**
* Process a CSS string
*
* @param {String} value
* CSS string e.g. "color:red; background-color:green;"
* @return {String}
* Converted CSS String e.g. "color:#F00; background-color:#0F0;"
*/
function processCSSString(value) {
if (value && /^""$/.test(value)) {
return value;
}
// This regex matches:
// - #F00
// - #FF0000
// - hsl()
// - hsla()
// - rgb()
// - rgba()
// - red
//
// It also matches css keywords e.g. "background-color" otherwise
// "background" would be replaced with #6363CE ("background" is a platform
// color).
let colorPattern = /#[0-9a-fA-F]{3}\b|#[0-9a-fA-F]{6}\b|hsl\(.*?\)|hsla\(.*?\)|rgba?\(.*?\)|\b[a-zA-Z-]+\b/g;
value = value.replace(colorPattern, function(match) {
let color = new CssColor(match);
if (color.valid) {
return color;
}
return match;
});
return value;
}
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});

View File

@@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MOCHITEST_BROWSER_FILES = \
browser_css_color.js \
browser_eventemitter_basic.js \
browser_observableobject.js \
browser_layoutHelpers.js \

View File

@@ -0,0 +1,310 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
let origColorUnit;
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
let {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
let {colorUtils} = devtools.require("devtools/shared/css-color");
function test() {
// FIXME: Enable this test on Linux once bug 916544 is fixed
if (Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS === "Linux") {
Services = colorUtils.CssColor = Loader = null;
return;
}
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(init, content);
}, true);
content.location = "data:text/html,browser_css_color.js";
}
function init() {
origColorUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
createDocument();
}
function createDocument()
{
let doc = content.document;
let canvas = doc.createElement("canvas");
canvas.width = canvas.height = 10;
doc.body.appendChild(canvas);
testColorUtils();
}
function testColorUtils() {
let data = getTestData();
for (let {authored, name, hex, hsl, rgb} of data) {
let color = new colorUtils.CssColor(authored);
// Check all values.
is(color.name, name, "color.name === name");
is(color.hex, hex, "color.hex === hex");
is(color.hsl, hsl, "color.hsl === hsl");
is(color.rgb, rgb, "color.rgb === rgb");
testToString(color, name, hex, hsl, rgb);
testColorMatch(name, hex, hsl, rgb, color.rgba);
}
testProcessCSSString();
finishUp();
}
function testToString(color, name, hex, hsl, rgb) {
switchColorUnit(colorUtils.CssColor.COLORUNIT.name);
is(color.toString(), name, "toString() with authored type");
switchColorUnit(colorUtils.CssColor.COLORUNIT.hex);
is(color.toString(), hex, "toString() with hex type");
switchColorUnit(colorUtils.CssColor.COLORUNIT.hsl);
is(color.toString(), hsl, "toString() with hsl type");
switchColorUnit(colorUtils.CssColor.COLORUNIT.rgb);
is(color.toString(), rgb, "toString() with rgb type");
}
function switchColorUnit(unit) {
Services.prefs.setCharPref(COLOR_UNIT_PREF, unit);
}
function testColorMatch(name, hex, hsl, rgb, rgba) {
let target;
let canvas = content.document.querySelector("canvas");
let ctx = canvas.getContext("2d");
let clearCanvas = function() {
canvas.width = 1;
};
let setColor = function(aColor) {
ctx.fillStyle = aColor;
ctx.fillRect(0, 0, 1, 1);
};
let setTargetColor = function() {
clearCanvas();
// All colors have rgba so we can use this to compare against.
setColor(rgba);
let [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
target = {r: r, g: g, b: b, a: a};
};
let test = function(aColor, type) {
let tolerance = 3; // hsla -> rgba -> hsla produces inaccurate results so we
// need some tolerence here.
clearCanvas();
setColor(aColor);
let [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
let rgbFail = Math.abs(r - target.r) > tolerance ||
Math.abs(g - target.g) > tolerance ||
Math.abs(b - target.b) > tolerance;
ok(!rgbFail, "color " + rgba + " matches target. Type: " + type);
if (rgbFail) {
info("target: " + (target.toSource()) + ", color: [r: " + r + ", g: " + g + ", b: " + b + ", a: " + a + "]");
}
let alphaFail = a !== target.a;
ok(!alphaFail, "color " + rgba + " alpha value matches target.");
};
setTargetColor();
test(name, "name");
test(hex, "hex");
test(hsl, "hsl");
test(rgb, "rgb");
switchColorUnit(origColorUnit);
}
function testProcessCSSString() {
let before = "border: 1px solid red; border-radius: 5px; " +
"color rgb(0, 255, 0); font-weight: bold; " +
"background-color: transparent; " +
"border-top-color: rgba(0, 0, 255, 0.5);";
let expected = "border: 1px solid #F00; border-radius: 5px; " +
"color #0F0; font-weight: bold; " +
"background-color: transparent; " +
"border-top-color: rgba(0, 0, 255, 0.5);";
let after = colorUtils.processCSSString(before);
is(after, expected, "CSS string processed correctly");
}
function finishUp() {
Services = colorUtils.CssColor = Loader = null;
gBrowser.removeCurrentTab();
finish();
}
function getTestData() {
return [
{authored: "aliceblue", name: "aliceblue", hex: "#F0F8FF", hsl: "hsl(208, 100%, 97%)", rgb: "rgb(240, 248, 255)"},
{authored: "antiquewhite", name: "antiquewhite", hex: "#FAEBD7", hsl: "hsl(34.286, 78%, 91%)", rgb: "rgb(250, 235, 215)"},
{authored: "aqua", name: "aqua", hex: "#0FF", hsl: "hsl(180, 100%, 50%)", rgb: "rgb(0, 255, 255)"},
{authored: "aquamarine", name: "aquamarine", hex: "#7FFFD4", hsl: "hsl(159.844, 100%, 75%)", rgb: "rgb(127, 255, 212)"},
{authored: "azure", name: "azure", hex: "#F0FFFF", hsl: "hsl(180, 100%, 97%)", rgb: "rgb(240, 255, 255)"},
{authored: "beige", name: "beige", hex: "#F5F5DC", hsl: "hsl(60, 56%, 91%)", rgb: "rgb(245, 245, 220)"},
{authored: "bisque", name: "bisque", hex: "#FFE4C4", hsl: "hsl(32.542, 100%, 88%)", rgb: "rgb(255, 228, 196)"},
{authored: "black", name: "black", hex: "#000", hsl: "hsl(0, 0%, 0%)", rgb: "rgb(0, 0, 0)"},
{authored: "blanchedalmond", name: "blanchedalmond", hex: "#FFEBCD", hsl: "hsl(36, 100%, 90%)", rgb: "rgb(255, 235, 205)"},
{authored: "blue", name: "blue", hex: "#00F", hsl: "hsl(240, 100%, 50%)", rgb: "rgb(0, 0, 255)"},
{authored: "blueviolet", name: "blueviolet", hex: "#8A2BE2", hsl: "hsl(271.148, 76%, 53%)", rgb: "rgb(138, 43, 226)"},
{authored: "brown", name: "brown", hex: "#A52A2A", hsl: "hsl(0, 59%, 41%)", rgb: "rgb(165, 42, 42)"},
{authored: "burlywood", name: "burlywood", hex: "#DEB887", hsl: "hsl(33.793, 57%, 70%)", rgb: "rgb(222, 184, 135)"},
{authored: "cadetblue", name: "cadetblue", hex: "#5F9EA0", hsl: "hsl(181.846, 25%, 50%)", rgb: "rgb(95, 158, 160)"},
{authored: "chartreuse", name: "chartreuse", hex: "#7FFF00", hsl: "hsl(90.118, 100%, 50%)", rgb: "rgb(127, 255, 0)"},
{authored: "chocolate", name: "chocolate", hex: "#D2691E", hsl: "hsl(25, 75%, 47%)", rgb: "rgb(210, 105, 30)"},
{authored: "coral", name: "coral", hex: "#FF7F50", hsl: "hsl(16.114, 100%, 66%)", rgb: "rgb(255, 127, 80)"},
{authored: "cornflowerblue", name: "cornflowerblue", hex: "#6495ED", hsl: "hsl(218.54, 79%, 66%)", rgb: "rgb(100, 149, 237)"},
{authored: "cornsilk", name: "cornsilk", hex: "#FFF8DC", hsl: "hsl(48, 100%, 93%)", rgb: "rgb(255, 248, 220)"},
{authored: "crimson", name: "crimson", hex: "#DC143C", hsl: "hsl(348, 83%, 47%)", rgb: "rgb(220, 20, 60)"},
{authored: "cyan", name: "aqua", hex: "#0FF", hsl: "hsl(180, 100%, 50%)", rgb: "rgb(0, 255, 255)"},
{authored: "darkblue", name: "darkblue", hex: "#00008B", hsl: "hsl(240, 100%, 27%)", rgb: "rgb(0, 0, 139)"},
{authored: "darkcyan", name: "darkcyan", hex: "#008B8B", hsl: "hsl(180, 100%, 27%)", rgb: "rgb(0, 139, 139)"},
{authored: "darkgoldenrod", name: "darkgoldenrod", hex: "#B8860B", hsl: "hsl(42.659, 89%, 38%)", rgb: "rgb(184, 134, 11)"},
{authored: "darkgray", name: "darkgray", hex: "#A9A9A9", hsl: "hsl(0, 0%, 66%)", rgb: "rgb(169, 169, 169)"},
{authored: "darkgreen", name: "darkgreen", hex: "#006400", hsl: "hsl(120, 100%, 20%)", rgb: "rgb(0, 100, 0)"},
{authored: "darkgrey", name: "darkgray", hex: "#A9A9A9", hsl: "hsl(0, 0%, 66%)", rgb: "rgb(169, 169, 169)"},
{authored: "darkkhaki", name: "darkkhaki", hex: "#BDB76B", hsl: "hsl(55.61, 38%, 58%)", rgb: "rgb(189, 183, 107)"},
{authored: "darkmagenta", name: "darkmagenta", hex: "#8B008B", hsl: "hsl(300, 100%, 27%)", rgb: "rgb(139, 0, 139)"},
{authored: "darkolivegreen", name: "darkolivegreen", hex: "#556B2F", hsl: "hsl(82, 39%, 30%)", rgb: "rgb(85, 107, 47)"},
{authored: "darkorange", name: "darkorange", hex: "#FF8C00", hsl: "hsl(32.941, 100%, 50%)", rgb: "rgb(255, 140, 0)"},
{authored: "darkorchid", name: "darkorchid", hex: "#9932CC", hsl: "hsl(280.13, 61%, 50%)", rgb: "rgb(153, 50, 204)"},
{authored: "darkred", name: "darkred", hex: "#8B0000", hsl: "hsl(0, 100%, 27%)", rgb: "rgb(139, 0, 0)"},
{authored: "darksalmon", name: "darksalmon", hex: "#E9967A", hsl: "hsl(15.135, 72%, 70%)", rgb: "rgb(233, 150, 122)"},
{authored: "darkseagreen", name: "darkseagreen", hex: "#8FBC8F", hsl: "hsl(120, 25%, 65%)", rgb: "rgb(143, 188, 143)"},
{authored: "darkslateblue", name: "darkslateblue", hex: "#483D8B", hsl: "hsl(248.462, 39%, 39%)", rgb: "rgb(72, 61, 139)"},
{authored: "darkslategray", name: "darkslategray", hex: "#2F4F4F", hsl: "hsl(180, 25%, 25%)", rgb: "rgb(47, 79, 79)"},
{authored: "darkslategrey", name: "darkslategray", hex: "#2F4F4F", hsl: "hsl(180, 25%, 25%)", rgb: "rgb(47, 79, 79)"},
{authored: "darkturquoise", name: "darkturquoise", hex: "#00CED1", hsl: "hsl(180.861, 100%, 41%)", rgb: "rgb(0, 206, 209)"},
{authored: "darkviolet", name: "darkviolet", hex: "#9400D3", hsl: "hsl(282.085, 100%, 41%)", rgb: "rgb(148, 0, 211)"},
{authored: "deeppink", name: "deeppink", hex: "#FF1493", hsl: "hsl(327.574, 100%, 54%)", rgb: "rgb(255, 20, 147)"},
{authored: "deepskyblue", name: "deepskyblue", hex: "#00BFFF", hsl: "hsl(195.059, 100%, 50%)", rgb: "rgb(0, 191, 255)"},
{authored: "dimgray", name: "dimgray", hex: "#696969", hsl: "hsl(0, 0%, 41%)", rgb: "rgb(105, 105, 105)"},
{authored: "dodgerblue", name: "dodgerblue", hex: "#1E90FF", hsl: "hsl(209.6, 100%, 56%)", rgb: "rgb(30, 144, 255)"},
{authored: "firebrick", name: "firebrick", hex: "#B22222", hsl: "hsl(0, 68%, 42%)", rgb: "rgb(178, 34, 34)"},
{authored: "floralwhite", name: "floralwhite", hex: "#FFFAF0", hsl: "hsl(40, 100%, 97%)", rgb: "rgb(255, 250, 240)"},
{authored: "forestgreen", name: "forestgreen", hex: "#228B22", hsl: "hsl(120, 61%, 34%)", rgb: "rgb(34, 139, 34)"},
{authored: "fuchsia", name: "fuchsia", hex: "#F0F", hsl: "hsl(300, 100%, 50%)", rgb: "rgb(255, 0, 255)"},
{authored: "gainsboro", name: "gainsboro", hex: "#DCDCDC", hsl: "hsl(0, 0%, 86%)", rgb: "rgb(220, 220, 220)"},
{authored: "ghostwhite", name: "ghostwhite", hex: "#F8F8FF", hsl: "hsl(240, 100%, 99%)", rgb: "rgb(248, 248, 255)"},
{authored: "gold", name: "gold", hex: "#FFD700", hsl: "hsl(50.588, 100%, 50%)", rgb: "rgb(255, 215, 0)"},
{authored: "goldenrod", name: "goldenrod", hex: "#DAA520", hsl: "hsl(42.903, 74%, 49%)", rgb: "rgb(218, 165, 32)"},
{authored: "gray", name: "gray", hex: "#808080", hsl: "hsl(0, 0%, 50%)", rgb: "rgb(128, 128, 128)"},
{authored: "green", name: "green", hex: "#008000", hsl: "hsl(120, 100%, 25%)", rgb: "rgb(0, 128, 0)"},
{authored: "greenyellow", name: "greenyellow", hex: "#ADFF2F", hsl: "hsl(83.654, 100%, 59%)", rgb: "rgb(173, 255, 47)"},
{authored: "grey", name: "gray", hex: "#808080", hsl: "hsl(0, 0%, 50%)", rgb: "rgb(128, 128, 128)"},
{authored: "honeydew", name: "honeydew", hex: "#F0FFF0", hsl: "hsl(120, 100%, 97%)", rgb: "rgb(240, 255, 240)"},
{authored: "hotpink", name: "hotpink", hex: "#FF69B4", hsl: "hsl(330, 100%, 71%)", rgb: "rgb(255, 105, 180)"},
{authored: "indianred", name: "indianred", hex: "#CD5C5C", hsl: "hsl(0, 53%, 58%)", rgb: "rgb(205, 92, 92)"},
{authored: "indigo", name: "indigo", hex: "#4B0082", hsl: "hsl(274.615, 100%, 25%)", rgb: "rgb(75, 0, 130)"},
{authored: "ivory", name: "ivory", hex: "#FFFFF0", hsl: "hsl(60, 100%, 97%)", rgb: "rgb(255, 255, 240)"},
{authored: "khaki", name: "khaki", hex: "#F0E68C", hsl: "hsl(54, 77%, 75%)", rgb: "rgb(240, 230, 140)"},
{authored: "lavender", name: "lavender", hex: "#E6E6FA", hsl: "hsl(240, 67%, 94%)", rgb: "rgb(230, 230, 250)"},
{authored: "lavenderblush", name: "lavenderblush", hex: "#FFF0F5", hsl: "hsl(340, 100%, 97%)", rgb: "rgb(255, 240, 245)"},
{authored: "lawngreen", name: "lawngreen", hex: "#7CFC00", hsl: "hsl(90.476, 100%, 49%)", rgb: "rgb(124, 252, 0)"},
{authored: "lemonchiffon", name: "lemonchiffon", hex: "#FFFACD", hsl: "hsl(54, 100%, 90%)", rgb: "rgb(255, 250, 205)"},
{authored: "lightblue", name: "lightblue", hex: "#ADD8E6", hsl: "hsl(194.737, 53%, 79%)", rgb: "rgb(173, 216, 230)"},
{authored: "lightcoral", name: "lightcoral", hex: "#F08080", hsl: "hsl(0, 79%, 72%)", rgb: "rgb(240, 128, 128)"},
{authored: "lightcyan", name: "lightcyan", hex: "#E0FFFF", hsl: "hsl(180, 100%, 94%)", rgb: "rgb(224, 255, 255)"},
{authored: "lightgoldenrodyellow", name: "lightgoldenrodyellow", hex: "#FAFAD2", hsl: "hsl(60, 80%, 90%)", rgb: "rgb(250, 250, 210)"},
{authored: "lightgray", name: "lightgray", hex: "#D3D3D3", hsl: "hsl(0, 0%, 83%)", rgb: "rgb(211, 211, 211)"},
{authored: "lightgreen", name: "lightgreen", hex: "#90EE90", hsl: "hsl(120, 73%, 75%)", rgb: "rgb(144, 238, 144)"},
{authored: "lightgrey", name: "lightgray", hex: "#D3D3D3", hsl: "hsl(0, 0%, 83%)", rgb: "rgb(211, 211, 211)"},
{authored: "lightpink", name: "lightpink", hex: "#FFB6C1", hsl: "hsl(350.959, 100%, 86%)", rgb: "rgb(255, 182, 193)"},
{authored: "lightsalmon", name: "lightsalmon", hex: "#FFA07A", hsl: "hsl(17.143, 100%, 74%)", rgb: "rgb(255, 160, 122)"},
{authored: "lightseagreen", name: "lightseagreen", hex: "#20B2AA", hsl: "hsl(176.712, 70%, 41%)", rgb: "rgb(32, 178, 170)"},
{authored: "lightskyblue", name: "lightskyblue", hex: "#87CEFA", hsl: "hsl(202.957, 92%, 75%)", rgb: "rgb(135, 206, 250)"},
{authored: "lightslategray", name: "lightslategray", hex: "#789", hsl: "hsl(210, 14%, 53%)", rgb: "rgb(119, 136, 153)"},
{authored: "lightslategrey", name: "lightslategray", hex: "#789", hsl: "hsl(210, 14%, 53%)", rgb: "rgb(119, 136, 153)"},
{authored: "lightsteelblue", name: "lightsteelblue", hex: "#B0C4DE", hsl: "hsl(213.913, 41%, 78%)", rgb: "rgb(176, 196, 222)"},
{authored: "lightyellow", name: "lightyellow", hex: "#FFFFE0", hsl: "hsl(60, 100%, 94%)", rgb: "rgb(255, 255, 224)"},
{authored: "lime", name: "lime", hex: "#0F0", hsl: "hsl(120, 100%, 50%)", rgb: "rgb(0, 255, 0)"},
{authored: "limegreen", name: "limegreen", hex: "#32CD32", hsl: "hsl(120, 61%, 50%)", rgb: "rgb(50, 205, 50)"},
{authored: "linen", name: "linen", hex: "#FAF0E6", hsl: "hsl(30, 67%, 94%)", rgb: "rgb(250, 240, 230)"},
{authored: "magenta", name: "fuchsia", hex: "#F0F", hsl: "hsl(300, 100%, 50%)", rgb: "rgb(255, 0, 255)"},
{authored: "maroon", name: "maroon", hex: "#800000", hsl: "hsl(0, 100%, 25%)", rgb: "rgb(128, 0, 0)"},
{authored: "mediumaquamarine", name: "mediumaquamarine", hex: "#66CDAA", hsl: "hsl(159.612, 51%, 60%)", rgb: "rgb(102, 205, 170)"},
{authored: "mediumblue", name: "mediumblue", hex: "#0000CD", hsl: "hsl(240, 100%, 40%)", rgb: "rgb(0, 0, 205)"},
{authored: "mediumorchid", name: "mediumorchid", hex: "#BA55D3", hsl: "hsl(288.095, 59%, 58%)", rgb: "rgb(186, 85, 211)"},
{authored: "mediumpurple", name: "mediumpurple", hex: "#9370DB", hsl: "hsl(259.626, 60%, 65%)", rgb: "rgb(147, 112, 219)"},
{authored: "mediumseagreen", name: "mediumseagreen", hex: "#3CB371", hsl: "hsl(146.723, 50%, 47%)", rgb: "rgb(60, 179, 113)"},
{authored: "mediumslateblue", name: "mediumslateblue", hex: "#7B68EE", hsl: "hsl(248.507, 80%, 67%)", rgb: "rgb(123, 104, 238)"},
{authored: "mediumspringgreen", name: "mediumspringgreen", hex: "#00FA9A", hsl: "hsl(156.96, 100%, 49%)", rgb: "rgb(0, 250, 154)"},
{authored: "mediumturquoise", name: "mediumturquoise", hex: "#48D1CC", hsl: "hsl(177.81, 60%, 55%)", rgb: "rgb(72, 209, 204)"},
{authored: "mediumvioletred", name: "mediumvioletred", hex: "#C71585", hsl: "hsl(322.247, 81%, 43%)", rgb: "rgb(199, 21, 133)"},
{authored: "midnightblue", name: "midnightblue", hex: "#191970", hsl: "hsl(240, 64%, 27%)", rgb: "rgb(25, 25, 112)"},
{authored: "mintcream", name: "mintcream", hex: "#F5FFFA", hsl: "hsl(150, 100%, 98%)", rgb: "rgb(245, 255, 250)"},
{authored: "mistyrose", name: "mistyrose", hex: "#FFE4E1", hsl: "hsl(6, 100%, 94%)", rgb: "rgb(255, 228, 225)"},
{authored: "moccasin", name: "moccasin", hex: "#FFE4B5", hsl: "hsl(38.108, 100%, 85%)", rgb: "rgb(255, 228, 181)"},
{authored: "navajowhite", name: "navajowhite", hex: "#FFDEAD", hsl: "hsl(35.854, 100%, 84%)", rgb: "rgb(255, 222, 173)"},
{authored: "navy", name: "navy", hex: "#000080", hsl: "hsl(240, 100%, 25%)", rgb: "rgb(0, 0, 128)"},
{authored: "oldlace", name: "oldlace", hex: "#FDF5E6", hsl: "hsl(39.13, 85%, 95%)", rgb: "rgb(253, 245, 230)"},
{authored: "olive", name: "olive", hex: "#808000", hsl: "hsl(60, 100%, 25%)", rgb: "rgb(128, 128, 0)"},
{authored: "olivedrab", name: "olivedrab", hex: "#6B8E23", hsl: "hsl(79.626, 60%, 35%)", rgb: "rgb(107, 142, 35)"},
{authored: "orange", name: "orange", hex: "#FFA500", hsl: "hsl(38.824, 100%, 50%)", rgb: "rgb(255, 165, 0)"},
{authored: "orangered", name: "orangered", hex: "#FF4500", hsl: "hsl(16.235, 100%, 50%)", rgb: "rgb(255, 69, 0)"},
{authored: "orchid", name: "orchid", hex: "#DA70D6", hsl: "hsl(302.264, 59%, 65%)", rgb: "rgb(218, 112, 214)"},
{authored: "palegoldenrod", name: "palegoldenrod", hex: "#EEE8AA", hsl: "hsl(54.706, 67%, 80%)", rgb: "rgb(238, 232, 170)"},
{authored: "palegreen", name: "palegreen", hex: "#98FB98", hsl: "hsl(120, 93%, 79%)", rgb: "rgb(152, 251, 152)"},
{authored: "paleturquoise", name: "paleturquoise", hex: "#AFEEEE", hsl: "hsl(180, 65%, 81%)", rgb: "rgb(175, 238, 238)"},
{authored: "palevioletred", name: "palevioletred", hex: "#DB7093", hsl: "hsl(340.374, 60%, 65%)", rgb: "rgb(219, 112, 147)"},
{authored: "papayawhip", name: "papayawhip", hex: "#FFEFD5", hsl: "hsl(37.143, 100%, 92%)", rgb: "rgb(255, 239, 213)"},
{authored: "peachpuff", name: "peachpuff", hex: "#FFDAB9", hsl: "hsl(28.286, 100%, 86%)", rgb: "rgb(255, 218, 185)"},
{authored: "peru", name: "peru", hex: "#CD853F", hsl: "hsl(29.577, 59%, 53%)", rgb: "rgb(205, 133, 63)"},
{authored: "pink", name: "pink", hex: "#FFC0CB", hsl: "hsl(349.524, 100%, 88%)", rgb: "rgb(255, 192, 203)"},
{authored: "plum", name: "plum", hex: "#DDA0DD", hsl: "hsl(300, 47%, 75%)", rgb: "rgb(221, 160, 221)"},
{authored: "powderblue", name: "powderblue", hex: "#B0E0E6", hsl: "hsl(186.667, 52%, 80%)", rgb: "rgb(176, 224, 230)"},
{authored: "purple", name: "purple", hex: "#800080", hsl: "hsl(300, 100%, 25%)", rgb: "rgb(128, 0, 128)"},
{authored: "red", name: "red", hex: "#F00", hsl: "hsl(0, 100%, 50%)", rgb: "rgb(255, 0, 0)"},
{authored: "rosybrown", name: "rosybrown", hex: "#BC8F8F", hsl: "hsl(0, 25%, 65%)", rgb: "rgb(188, 143, 143)"},
{authored: "royalblue", name: "royalblue", hex: "#4169E1", hsl: "hsl(225, 73%, 57%)", rgb: "rgb(65, 105, 225)"},
{authored: "saddlebrown", name: "saddlebrown", hex: "#8B4513", hsl: "hsl(25, 76%, 31%)", rgb: "rgb(139, 69, 19)"},
{authored: "salmon", name: "salmon", hex: "#FA8072", hsl: "hsl(6.176, 93%, 71%)", rgb: "rgb(250, 128, 114)"},
{authored: "sandybrown", name: "sandybrown", hex: "#F4A460", hsl: "hsl(27.568, 87%, 67%)", rgb: "rgb(244, 164, 96)"},
{authored: "seagreen", name: "seagreen", hex: "#2E8B57", hsl: "hsl(146.452, 50%, 36%)", rgb: "rgb(46, 139, 87)"},
{authored: "seashell", name: "seashell", hex: "#FFF5EE", hsl: "hsl(24.706, 100%, 97%)", rgb: "rgb(255, 245, 238)"},
{authored: "sienna", name: "sienna", hex: "#A0522D", hsl: "hsl(19.304, 56%, 40%)", rgb: "rgb(160, 82, 45)"},
{authored: "silver", name: "silver", hex: "#C0C0C0", hsl: "hsl(0, 0%, 75%)", rgb: "rgb(192, 192, 192)"},
{authored: "skyblue", name: "skyblue", hex: "#87CEEB", hsl: "hsl(197.4, 71%, 73%)", rgb: "rgb(135, 206, 235)"},
{authored: "slateblue", name: "slateblue", hex: "#6A5ACD", hsl: "hsl(248.348, 53%, 58%)", rgb: "rgb(106, 90, 205)"},
{authored: "slategray", name: "slategray", hex: "#708090", hsl: "hsl(210, 13%, 50%)", rgb: "rgb(112, 128, 144)"},
{authored: "slategrey", name: "slategray", hex: "#708090", hsl: "hsl(210, 13%, 50%)", rgb: "rgb(112, 128, 144)"},
{authored: "snow", name: "snow", hex: "#FFFAFA", hsl: "hsl(0, 100%, 99%)", rgb: "rgb(255, 250, 250)"},
{authored: "springgreen", name: "springgreen", hex: "#00FF7F", hsl: "hsl(149.882, 100%, 50%)", rgb: "rgb(0, 255, 127)"},
{authored: "steelblue", name: "steelblue", hex: "#4682B4", hsl: "hsl(207.273, 44%, 49%)", rgb: "rgb(70, 130, 180)"},
{authored: "tan", name: "tan", hex: "#D2B48C", hsl: "hsl(34.286, 44%, 69%)", rgb: "rgb(210, 180, 140)"},
{authored: "teal", name: "teal", hex: "#008080", hsl: "hsl(180, 100%, 25%)", rgb: "rgb(0, 128, 128)"},
{authored: "thistle", name: "thistle", hex: "#D8BFD8", hsl: "hsl(300, 24%, 80%)", rgb: "rgb(216, 191, 216)"},
{authored: "tomato", name: "tomato", hex: "#FF6347", hsl: "hsl(9.13, 100%, 64%)", rgb: "rgb(255, 99, 71)"},
{authored: "turquoise", name: "turquoise", hex: "#40E0D0", hsl: "hsl(174, 72%, 56%)", rgb: "rgb(64, 224, 208)"},
{authored: "violet", name: "violet", hex: "#EE82EE", hsl: "hsl(300, 76%, 72%)", rgb: "rgb(238, 130, 238)"},
{authored: "wheat", name: "wheat", hex: "#F5DEB3", hsl: "hsl(39.091, 77%, 83%)", rgb: "rgb(245, 222, 179)"},
{authored: "white", name: "white", hex: "#FFF", hsl: "hsl(0, 0%, 100%)", rgb: "rgb(255, 255, 255)"},
{authored: "whitesmoke", name: "whitesmoke", hex: "#F5F5F5", hsl: "hsl(0, 0%, 96%)", rgb: "rgb(245, 245, 245)"},
{authored: "yellow", name: "yellow", hex: "#FF0", hsl: "hsl(60, 100%, 50%)", rgb: "rgb(255, 255, 0)"},
{authored: "yellowgreen", name: "yellowgreen", hex: "#9ACD32", hsl: "hsl(79.742, 61%, 50%)", rgb: "rgb(154, 205, 50)"},
{authored: "transparent", name: "transparent", hex: "transparent", hsl: "transparent", rgb: "transparent"},
{authored: "rgba(0, 0, 0, 0)", name: "transparent", hex: "transparent", hsl: "transparent", rgb: "transparent"},
{authored: "hsla(0, 0%, 0%, 0)", name: "transparent", hex: "transparent", hsl: "transparent", rgb: "transparent"},
{authored: "rgba(50, 60, 70, 0.5)", name: "rgba(50, 60, 70, 0.5)", hex: "rgba(50, 60, 70, 0.5)", hsl: "hsla(210, 17%, 24%, 0.5)", rgb: "rgba(50, 60, 70, 0.5)"},
{authored: "rgba(0, 0, 0, 0.3)", name: "rgba(0, 0, 0, 0.3)", hex: "rgba(0, 0, 0, 0.3)", hsl: "hsla(0, 0%, 0%, 0.3)", rgb: "rgba(0, 0, 0, 0.3)"},
{authored: "rgba(255, 255, 255, 0.7)", name: "rgba(255, 255, 255, 0.7)", hex: "rgba(255, 255, 255, 0.7)", hsl: "hsla(0, 0%, 100%, 0.7)", rgb: "rgba(255, 255, 255, 0.7)"},
{authored: "rgba(127, 89, 45, 1)", name: "#7F592D", hex: "#7F592D", hsl: "hsl(32.195, 48%, 34%)", rgb: "rgb(127, 89, 45)"},
{authored: "hsla(19.304, 56%, 40%, 1)", name: "#9F512C", hex: "#9F512C", hsl: "hsl(19.304, 57%, 40%)", rgb: "rgb(159, 81, 44)"},
{authored: "invalidColor", name: "", hex: "", hsl: "", rgb: ""}
];
}

View File

@@ -11,13 +11,14 @@ let {CssLogic} = require("devtools/styleinspector/css-logic");
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
let promise = require("sdk/core/promise");
let {EventEmitter} = require("devtools/shared/event-emitter");
let {colorUtils} = require("devtools/shared/css-color");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PluralForm.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const FILTER_CHANGED_TIMEOUT = 300;
@@ -152,6 +153,10 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
// No results text.
this.noResults = this.styleDocument.getElementById("noResults");
// Refresh panel when color unit changed.
this._handlePrefChange = this._handlePrefChange.bind(this);
gDevTools.on("pref-changed", this._handlePrefChange);
CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
// The element that we're inspecting, and the document that it comes from.
@@ -244,6 +249,12 @@ CssHtmlTree.prototype = {
return this.includeBrowserStylesCheckbox.checked;
},
_handlePrefChange: function(event, data) {
if (data.pref == "devtools.defaultColorUnit" && this._computed) {
this.refreshPanel();
}
},
/**
* Update the highlighted element. The CssHtmlTree panel will show the style
* information for the given element.
@@ -256,7 +267,7 @@ CssHtmlTree.prototype = {
if (this._refreshProcess) {
this._refreshProcess.cancel();
}
return promise.resolve(undefined)
return promise.resolve(undefined);
}
if (aElement === this.viewedElement) {
@@ -495,6 +506,7 @@ CssHtmlTree.prototype = {
this.includeBrowserStylesCheckbox.removeEventListener("command",
this.includeBrowserStylesChanged);
this.searchField.removeEventListener("command", this.filterChanged);
gDevTools.off("pref-changed", this._handlePrefChange);
// Cancel tree construction
if (this._createViewsProcess) {
@@ -553,8 +565,13 @@ function PropertyInfo(aTree, aName) {
this.name = aName;
}
PropertyInfo.prototype = {
get value() this.tree._computed ? this.tree._computed[this.name].value : ""
}
get value() {
if (this.tree._computed) {
let value = this.tree._computed[this.name].value;
return colorUtils.processCSSString(value);
}
}
};
/**
* A container to give easy access to property data from the template engine.
@@ -935,7 +952,8 @@ SelectorView.prototype = {
get value()
{
return this.selectorInfo.value;
let val = this.selectorInfo.value;
return colorUtils.processCSSString(val);
},
maybeOpenStyleEditor: function(aEvent)

View File

@@ -12,6 +12,8 @@ const promise = require("sdk/core/promise");
let {CssLogic} = require("devtools/styleinspector/css-logic");
let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
let {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
let {colorUtils} = require("devtools/shared/css-color");
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -910,7 +912,7 @@ function TextProperty(aRule, aName, aValue, aPriority)
{
this.rule = aRule;
this.name = aName;
this.value = aValue;
this.value = colorUtils.processCSSString(aValue);
this.priority = aPriority;
this.enabled = true;
this.updateComputed();
@@ -1000,7 +1002,7 @@ TextProperty.prototype = {
remove: function TextProperty_remove()
{
this.rule.removeProperty(this);
}
},
};
@@ -1048,6 +1050,9 @@ function CssRuleView(aDoc, aStore, aPageStyle)
this._boundCopy = this._onCopy.bind(this);
this.element.addEventListener("copy", this._boundCopy);
this._handlePrefChange = this._handlePrefChange.bind(this);
gDevTools.on("pref-changed", this._handlePrefChange);
let options = {
fixedWidth: true,
autoSelect: true,
@@ -1075,10 +1080,20 @@ CssRuleView.prototype = {
return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0;
},
_handlePrefChange: function(event, data) {
if (data.pref == "devtools.defaultColorUnit") {
let element = this._viewedElement;
this._viewedElement = null;
this.highlight(element);
}
},
destroy: function CssRuleView_destroy()
{
this.clear();
gDevTools.off("pref-changed", this._handlePrefChange);
this.element.removeEventListener("copy", this._boundCopy);
delete this._boundCopy;

View File

@@ -71,7 +71,7 @@ function checkCopySelection()
info("Checking that cssHtmlTree.siBoundCopy() " +
" returns the correct clipboard value");
let expectedPattern = "color: rgb\\(255, 255, 0\\);[\\r\\n]+" +
let expectedPattern = "color: #FF0;[\\r\\n]+" +
"font-family: helvetica,sans-serif;[\\r\\n]+" +
"font-size: 16px;[\\r\\n]+" +
"font-variant: small-caps;[\\r\\n]*";

View File

@@ -9,7 +9,7 @@ let doc;
let ruleWindow;
let ruleView;
let inspector;
let originalValue = "blue";
let originalValue = "#00F";
// Test data format
// {
@@ -54,7 +54,7 @@ function runTestData(index)
let idRuleEditor = ruleView.element.children[1]._ruleEditor;
let propEditor = idRuleEditor.rule.textProps[0].editor;
waitForEditorFocus(propEditor.element, function(aEditor) {
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focused editor should be the value.");
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focused editor should be the value span.");
for (let ch of testData[index].value) {
EventUtils.sendChar(ch, ruleWindow);

View File

@@ -71,10 +71,10 @@ function checkCopySelection()
let expectedPattern = " margin: 10em;[\\r\\n]+" +
" font-size: 14pt;[\\r\\n]+" +
" font-family: helvetica,sans-serif;[\\r\\n]+" +
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
" color: #AAA;[\\r\\n]+" +
"}[\\r\\n]+" +
"html {[\\r\\n]+" +
" color: rgb\\(0, 0, 0\\);[\\r\\n]*";
" color: #000;[\\r\\n]*";
SimpleTest.waitForClipboard(function IUI_boundCopyCheck() {
return checkClipboardData(expectedPattern);

View File

@@ -4,8 +4,6 @@
let doc;
function simpleOverride(aInspector, aRuleView)
{
doc.body.innerHTML = '<div id="testid">Styled Node</div>';
@@ -22,17 +20,17 @@ function simpleOverride(aInspector, aRuleView)
is(elementRule.textProps[1], secondProp, "Rules should be in addition order.");
promiseDone(elementRule._applyingModifications.then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Second property should have been used.");
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Second property should have been used.");
secondProp.remove();
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "green", "After deleting second property, first should be used.");
is(element.style.getPropertyValue("background-color"), "rgb(0, 128, 0)", "After deleting second property, first should be used.");
secondProp = elementRule.createProperty("background-color", "blue", "");
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "New property should be used.");
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "New property should be used.");
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
@@ -40,7 +38,7 @@ function simpleOverride(aInspector, aRuleView)
secondProp.setEnabled(false);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "green", "After disabling second property, first value should be used");
is(element.style.getPropertyValue("background-color"), "rgb(0, 128, 0)", "After disabling second property, first value should be used");
firstProp.setEnabled(false);
return elementRule._applyingModifications;
@@ -50,19 +48,19 @@ function simpleOverride(aInspector, aRuleView)
secondProp.setEnabled(true);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Value should be set correctly after re-enabling");
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Value should be set correctly after re-enabling");
firstProp.setEnabled(true);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Re-enabling an earlier property shouldn't make it override a later property.");
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Re-enabling an earlier property shouldn't make it override a later property.");
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
firstProp.setValue("purple", "");
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Modifying an earlier property shouldn't override a later property.");
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Modifying an earlier property shouldn't override a later property.");
finishTest();
}));
});

View File

@@ -67,7 +67,7 @@ function testTopLeft()
is
(
convertTextPropsToString(elementAfterRule.textProps),
"background: none repeat scroll 0% 0% red; content: \" \"; position: absolute; " +
"background: none repeat scroll 0% 0% #F00; content: \" \"; position: absolute; " +
"border-radius: 50%; height: 32px; width: 32px; top: 50%; left: 50%; margin-top: -16px; margin-left: -16px",
"TopLeft after properties are correct"
);
@@ -242,7 +242,7 @@ function testParagraph()
is
(
convertTextPropsToString(elementFirstLineRule.textProps),
"background: none repeat scroll 0% 0% blue",
"background: none repeat scroll 0% 0% #00F",
"Paragraph first-line properties are correct"
);
@@ -254,7 +254,7 @@ function testParagraph()
is
(
convertTextPropsToString(elementFirstLetterRule.textProps),
"color: red; font-size: 130%",
"color: #F00; font-size: 130%",
"Paragraph first-letter properties are correct"
);
@@ -266,7 +266,7 @@ function testParagraph()
is
(
convertTextPropsToString(elementSelectionRule.textProps),
"color: white; background: none repeat scroll 0% 0% black",
"color: #FFF; background: none repeat scroll 0% 0% #000",
"Paragraph first-letter properties are correct"
);

View File

@@ -21,6 +21,37 @@
- the heading of the advanced settings group in the options panel. -->
<!ENTITY options.context.advancedSettings "Advanced settings">
<!-- LOCALIZATION NOTE (options.context.inspector): This is the label for
- the heading of the Inspector group in the options panel. -->
<!ENTITY options.context.inspector "Inspector">
<!-- LOCALIZATION NOTE (options.defaultColorUnit.label): This is the label for a
- dropdown list that controls the default color unit used in the inspector.
- This label is visible in the options panel. -->
<!ENTITY options.defaultColorUnit.label "Default color unit">
<!-- LOCALIZATION NOTE (options.defaultColorUnit.accesskey): This is the access
- key for a dropdown list that controls the default color unit used in the
- inspector. This is visible in the options panel. -->
<!ENTITY options.defaultColorUnit.accesskey "U">
<!-- LOCALIZATION NOTE (options.defaultColorUnit.hex): This is used in the
- 'Default color unit' dropdown list and is visible in the options panel. -->
<!ENTITY options.defaultColorUnit.hex "Hex">
<!-- LOCALIZATION NOTE (options.defaultColorUnit.hsl): This is used in the
- 'Default color unit' dropdown list and is visible in the options panel. -->
<!ENTITY options.defaultColorUnit.hsl "HSL(A)">
<!-- LOCALIZATION NOTE (options.defaultColorUnit.rgb): This is used in the
- 'Default color unit' dropdown list and is visible in the options panel. -->
<!ENTITY options.defaultColorUnit.rgb "RGB(A)">
<!-- LOCALIZATION NOTE (options.defaultColorUnit.name): This is used in
- the 'Default color unit' dropdown list and is visible in the options panel.
- -->
<!ENTITY options.defaultColorUnit.name "Color Names">
<!-- LOCALIZATION NOTE (options.context.requiresRestart2): This is the requires
- restart label at right of settings that require a browser restart to be
- effective. -->

View File

@@ -51,6 +51,8 @@ const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let {colorUtils} = require("devtools/shared/css-color");
function CssLogic()
{
// The cache of examined CSS properties.
@@ -1452,7 +1454,6 @@ CssPropertyInfo.prototype = {
Services.console.logStringMessage(ex);
}
}
return this._value;
},
@@ -1601,9 +1602,8 @@ function CssSelectorInfo(aSelector, aProperty, aValue, aStatus)
{
this.selector = aSelector;
this.property = aProperty;
this.value = aValue;
this.status = aStatus;
this.value = colorUtils.processCSSString(aValue);
let priority = this.selector.cssRule.getPropertyPriority(this.property);
this.important = (priority === "important");
}