Bug 1035106 - Add new APIs to DOMUtils: colorToRGBA, isValidCSSColor & cssPropertyIsValid r=bz

This commit is contained in:
Michael Ratcliffe 2014-07-17 15:08:07 +01:00
parent 47d574ac55
commit 5191e86267
9 changed files with 411 additions and 7 deletions

View File

@ -14,3 +14,16 @@ dictionary InspectorRGBTriple {
octet g = 0;
octet b = 0;
};
dictionary InspectorRGBATuple {
/*
* NOTE: This tuple is in the normal 0-255-sized RGB space but can be
* fractional and may extend outside the 0-255 range.
*
* a is in the range 0 - 1.
*/
double r = 0;
double g = 0;
double b = 0;
double a = 1;
};

View File

@ -39,9 +39,12 @@
#include "nsCSSRuleProcessor.h"
#include "mozilla/dom/InspectorUtilsBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsCSSParser.h"
#include "nsCSSProps.h"
#include "nsCSSValue.h"
#include "nsColor.h"
#include "nsStyleSet.h"
#include "nsStyleUtil.h"
using namespace mozilla;
using namespace mozilla::css;
@ -795,6 +798,66 @@ inDOMUtils::RgbToColorName(uint8_t aR, uint8_t aG, uint8_t aB,
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::ColorToRGBA(const nsAString& aColorString, JSContext* aCx,
JS::MutableHandle<JS::Value> aValue)
{
nscolor color = 0;
nsCSSParser cssParser;
nsCSSValue cssValue;
bool isColor = cssParser.ParseColorString(aColorString, nullptr, 0,
cssValue, true);
if (!isColor) {
aValue.setNull();
return NS_OK;
}
nsRuleNode::ComputeColor(cssValue, nullptr, nullptr, color);
InspectorRGBATuple tuple;
tuple.mR = NS_GET_R(color);
tuple.mG = NS_GET_G(color);
tuple.mB = NS_GET_B(color);
tuple.mA = nsStyleUtil::ColorComponentToFloat(NS_GET_A(color));
if (!ToJSValue(aCx, tuple, aValue)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::IsValidCSSColor(const nsAString& aColorString, bool *_retval)
{
nsCSSParser cssParser;
nsCSSValue cssValue;
*_retval = cssParser.ParseColorString(aColorString, nullptr, 0, cssValue, true);
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::CssPropertyIsValid(const nsAString& aPropertyName,
const nsAString& aPropertyValue,
bool *_retval)
{
nsCSSProperty propertyID =
nsCSSProps::LookupProperty(aPropertyName, nsCSSProps::eIgnoreEnabledState);
if (propertyID == eCSSProperty_UNKNOWN) {
*_retval = false;
return NS_OK;
}
// Get a parser, parse the property.
nsCSSParser parser;
*_retval = parser.IsValueValidForProperty(propertyID, aPropertyValue);
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::GetBindingURLs(nsIDOMElement *aElement, nsIArray **_retval)
{

View File

@ -17,7 +17,7 @@ interface nsIDOMFontFaceList;
interface nsIDOMRange;
interface nsIDOMCSSStyleSheet;
[scriptable, uuid(fd529e53-f734-4d15-83ce-d545a631d668)]
[scriptable, uuid(bd6b3dee-b8dd-40c7-a40a-ad8455b49917)]
interface inIDOMUtils : nsISupports
{
// CSS utilities
@ -69,8 +69,25 @@ interface inIDOMUtils : nsISupports
jsval colorNameToRGB(in DOMString aColorName);
AString rgbToColorName(in octet aR, in octet aG, in octet aB);
// Convert a given CSS color string to rgba. Returns null on failure or an
// InspectorRGBATuple on success.
//
// NOTE: Converting a color to RGBA may be lossy when converting from some
// formats e.g. CMYK.
[implicit_jscontext]
jsval colorToRGBA(in DOMString aColorString);
// Check whether a given color is a valid CSS color.
bool isValidCSSColor(in AString aColorString);
// Utilities for obtaining information about a CSS property.
// Check whether a CSS property and value are a valid combination. If the
// property is pref-disabled it will still be processed.
// aPropertyName: A property name e.g. "color"
// aPropertyValue: A property value e.g. "red" or "red !important"
bool cssPropertyIsValid(in AString aPropertyName, in AString aPropertyValue);
// Get a list of the longhands corresponding to the given CSS property. If
// the property is a longhand already, just returns the property itself.
// Throws on unsupported property names.

View File

@ -12,5 +12,8 @@ support-files = bug856317.css
[test_bug856317.html]
[test_bug877690.html]
[test_bug1006595.html]
[test_color_to_rgba.html]
[test_is_valid_css_color.html]
[test_css_property_is_valid.html]
[test_get_all_style_sheets.html]
[test_isinheritableproperty.html]

View File

@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test inDOMUtils::ColorToRGBA</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8">
let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
testColor("red", {r:255, g:0, b:0, a:1});
testColor("#f00", {r:255, g:0, b:0, a:1});
testColor("#ff0000", {r:255, g:0, b:0, a:1});
testColor("ff0000", null);
testColor("rgb(255,0,0)", {r:255, g:0, b:0, a:1});
testColor("rgba(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
testColor("rgb(255,0,0,0.7)", null);
testColor("rgb(50%,75%,60%)", {r:128, g:191, b:153, a:1});
testColor("rgba(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
testColor("hsl(320,30%,10%)", {r:33, g:17, b:28, a:1});
testColor("hsla(170,60%,40%,0.9)", {r:40, g:163, b:142, a:0.9});
function testColor(color, expected) {
let rgb = utils.colorToRGBA(color);
if (rgb === null) {
ok(expected === null, "color: " + color + " returns null");
return;
}
let {r, g, b, a} = rgb;
is(r, expected.r, "color: " + color + ", red component is converted correctly");
is(g, expected.g, "color: " + color + ", green component is converted correctly");
is(b, expected.b, "color: " + color + ", blue component is converted correctly");
is(Math.round(a * 10) / 10, expected.a, "color: " + color + ", alpha component is a converted correctly");
}
</script>
</head>
<body>
<h1>Test inDOMUtils::ColorToRGBA</h1>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test inDOMUtils::CssPropertyIsValid</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8">
let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
let tests = [
{
property: "color",
value: "red",
expected: true
},
{
property: "display",
value: "none",
expected: true
},
{
property: "display",
value: "red",
expected: false
},
{
property: "displayx",
value: "none",
expected: false
},
{
property: "border",
value: "1px solid blue",
expected: true
},
{
property: "border",
value: "1 solid blue",
expected: false
},
{
property: "border",
value: "1px underline blue",
expected: false
},
{
property: "border",
value: "1px solid",
expected: true
},
{
property: "color",
value: "blue !important",
expected: true
},
{
property: "color",
value: "blue ! important",
expected: true
},
{
property: "color",
value: "blue !impoxxxrtant",
expected: false
},
{
property: "color",
value: "red; background:green;",
expected: false
},
{
property: "content",
value: "\"hello\"",
expected: true
}
];
for (let {property, value, expected} of tests) {
let valid = utils.cssPropertyIsValid(property, value);
if (expected) {
ok(valid, property + ":" + value + " is valid");
} else {
ok(!valid, property + ":" + value + " is not valid");
}
}
</script>
</head>
<body>
<h1>Test inDOMUtils::CssPropertyIsValid</h1>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test inDOMUtils::isValidCSSColor</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8">
let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
// Color names
let colors = utils.getCSSValuesForProperty("color");
let notColor = ["hsl", "hsla", "inherit", "initial", "rgb", "rgba", "unset"];
for (let color of colors) {
if (notColor.indexOf(color) !== -1) {
continue;
}
ok(utils.isValidCSSColor(color), color + " is a valid color");
ok(!utils.isValidCSSColor("xxx" + color), "xxx" + color + " is not a valid color");
}
// rgb(a)
for (let i = 0; i <= 265; i++) {
ok(utils.isValidCSSColor("rgb(" + i + ",0,0)"), "rgb(" + i + ",0,0) is a valid color");
ok(utils.isValidCSSColor("rgb(0," + i + ",0)"), "rgb(0," + i + ",0) is a valid color");
ok(utils.isValidCSSColor("rgb(0,0," + i + ")"), "rgb(0,0," + i + ") is a valid color");
ok(utils.isValidCSSColor("rgba(" + i + ",0,0,0.2)"), "rgba(" + i + ",0,0,0.2) is a valid color");
ok(utils.isValidCSSColor("rgba(0," + i + ",0,0.5)"), "rgba(0," + i + ",0,0.5) is a valid color");
ok(utils.isValidCSSColor("rgba(0,0," + i + ",0.7)"), "rgba(0,0," + i + ",0.7) is a valid color");
ok(!utils.isValidCSSColor("rgbxxx(" + i + ",0,0)"), "rgbxxx(" + i + ",0,0) is not a valid color");
ok(!utils.isValidCSSColor("rgbxxx(0," + i + ",0)"), "rgbxxx(0," + i + ",0) is not a valid color");
ok(!utils.isValidCSSColor("rgbxxx(0,0," + i + ")"), "rgbxxx(0,0," + i + ") is not a valid color");
}
// rgb(a) (%)
for (let i = 0; i <= 110; i++) {
ok(utils.isValidCSSColor("rgb(" + i + "%,0%,0%)"), "rgb(" + i + "%,0%,0%) is a valid color");
ok(utils.isValidCSSColor("rgb(0%," + i + "%,0%)"), "rgb(0%," + i + "%,0%) is a valid color");
ok(utils.isValidCSSColor("rgb(0%,0%," + i + "%)"), "rgb(0%,0%," + i + "%) is a valid color");
ok(utils.isValidCSSColor("rgba(" + i + "%,0%,0%,0.2)"), "rgba(" + i + "%,0%,0%,0.2) is a valid color");
ok(utils.isValidCSSColor("rgba(0%," + i + "%,0%,0.5)"), "rgba(0%," + i + "%,0%,0.5) is a valid color");
ok(utils.isValidCSSColor("rgba(0%,0%," + i + "%,0.7)"), "rgba(0%,0%," + i + "%,0.7) is a valid color");
ok(!utils.isValidCSSColor("rgbaxxx(" + i + "%,0%,0%,0.2)"), "rgbaxxx(" + i + "%,0%,0%,0.2) is not a valid color");
ok(!utils.isValidCSSColor("rgbaxxx(0%," + i + "%,0%,0.5)"), "rgbaxxx(0%," + i + "%,0%,0.5) is not a valid color");
ok(!utils.isValidCSSColor("rgbaxxx(0%,0%," + i + "%,0.7)"), "rgbaxxx(0%,0%," + i + "%,0.7) is not a valid color");
}
// hsl(a)
for (let i = 0; i <= 370; i++) {
ok(utils.isValidCSSColor("hsl(" + i + ",30%,10%)"), "rgb(" + i + ",30%,10%) is a valid color");
ok(utils.isValidCSSColor("hsla(" + i + ",60%,70%,0.2)"), "rgba(" + i + ",60%,70%,0.2) is a valid color");
}
for (let i = 0; i <= 110; i++) {
ok(utils.isValidCSSColor("hsl(100," + i + "%,20%)"), "hsl(100," + i + "%,20%) is a valid color");
ok(utils.isValidCSSColor("hsla(100,20%," + i + "%,0.6)"), "hsla(100,20%," + i + "%,0.6) is a valid color");
}
// hex
for (let i = 0; i <= 255; i++) {
let hex = (i).toString(16);
if (hex.length === 1) {
hex = 0 + hex;
}
ok(utils.isValidCSSColor("#" + hex + "7777"), "#" + hex + "7777 is a valid color");
ok(utils.isValidCSSColor("#77" + hex + "77"), "#77" + hex + "77 is a valid color");
ok(utils.isValidCSSColor("#7777" + hex), "#7777" + hex + " is a valid color");
}
ok(!utils.isValidCSSColor("#kkkkkk"), "#kkkkkk is not a valid color");
// short hex
for (let i = 0; i <= 16; i++) {
let hex = (i).toString(16);
ok(utils.isValidCSSColor("#" + hex + hex + hex), "#" + hex + hex + hex + " is a valid color");
}
ok(!utils.isValidCSSColor("#ggg"), "#ggg is not a valid color");
</script>
</head>
<body>
<h1>Test inDOMUtils::isValidCSSColor</h1>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -177,7 +177,8 @@ public:
bool ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue);
nsCSSValue& aValue,
bool aSuppressErrors /* false */);
nsresult ParseSelectorString(const nsSubstring& aSelectorString,
nsIURI* aURL, // for error reporting
@ -216,6 +217,8 @@ public:
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
bool IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue);
typedef nsCSSParser::VariableEnumFunc VariableEnumFunc;
@ -1707,15 +1710,24 @@ bool
CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue)
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
// Parse a color, and check that there's nothing else after it.
bool colorParsed = ParseColor(aValue) && !GetToken(true);
OUTPUT_ERROR();
if (aSuppressErrors) {
CLEAR_ERROR();
} else {
OUTPUT_ERROR();
}
ReleaseScanner();
return colorParsed;
}
@ -14668,6 +14680,47 @@ CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
return true;
}
bool
CSSParserImpl::IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue)
{
mData.AssertInitialState();
mTempData.AssertInitialState();
nsCSSScanner scanner(aPropValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
nsAutoSuppressErrors suppressErrors(this);
mSection = eCSSSection_General;
scanner.SetSVGMode(false);
// Check for unknown properties
if (eCSSProperty_UNKNOWN == aPropID) {
ReleaseScanner();
return false;
}
// Check that the property and value parse successfully
bool parsedOK = ParseProperty(aPropID);
// Check for priority
parsedOK = parsedOK && ParsePriority() != ePriority_Error;
// We should now be at EOF
parsedOK = parsedOK && !GetToken(true);
mTempData.ClearProperty(aPropID);
mTempData.AssertInitialState();
mData.AssertInitialState();
CLEAR_ERROR();
ReleaseScanner();
return parsedOK;
}
} // anonymous namespace
// Recycling of parser implementation objects
@ -14860,10 +14913,11 @@ bool
nsCSSParser::ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSValue& aValue)
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseColorString(aBuffer, aURI, aLineNumber, aValue);
ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}
nsresult
@ -14981,3 +15035,12 @@ nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
ParseCounterDescriptor(aDescID, aBuffer,
aSheetURL, aBaseURL, aSheetPrincipal, aValue);
}
bool
nsCSSParser::IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue)
{
return static_cast<CSSParserImpl*>(mImpl)->
IsValueValidForProperty(aPropID, aPropValue);
}

View File

@ -191,7 +191,8 @@ public:
bool ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURL,
uint32_t aLineNumber,
nsCSSValue& aValue);
nsCSSValue& aValue,
bool aSuppressErrors = false);
/**
* Parse aBuffer into a selector list. On success, caller must
@ -296,6 +297,10 @@ public:
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
// Check whether a given value can be applied to a property.
bool IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue);
protected:
// This is a CSSParserImpl*, but if we expose that type name in this
// header, we can't put the type definition (in nsCSSParser.cpp) in