Bug 1290864 - Standardize serialization of <position> values in <basic-shape>; r?xidorn
MozReview-Commit-ID: KooW1OqtTUb
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1323,16 +1323,20 @@ protected:
return mParsingCompoundProperty;
}
/* Functions for basic shapes */
bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
bool ParsePolygonFunction(nsCSSValue& aValue);
bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
bool ParseInsetFunction(nsCSSValue& aValue);
+ // We parse position values differently for basic-shape, by expanding defaults
+ // and replacing keywords with percentages
+ bool ParsePositionValueForBasicShape(nsCSSValue& aOut);
+
/* Functions for transform Parsing */
bool ParseSingleTransform(bool aIsPrefixed, bool aDisallowRelativeValues,
nsCSSValue& aValue);
bool ParseFunction(nsCSSKeyword aFunction, const uint32_t aAllowedTypes[],
uint32_t aVariantMaskAll, uint16_t aMinElems,
uint16_t aMaxElems, nsCSSValue &aValue);
bool ParseFunctionInternals(const uint32_t aVariantMask[],
@@ -12563,16 +12567,21 @@ bool CSSParserImpl::ParseBoxPositionValu
// Create style values
xValue = BoxPositionMaskToCSSValue(mask, true);
yValue = BoxPositionMaskToCSSValue(mask, false);
return true;
}
// Parses a CSS <position> value, for e.g. the 'background-position' property.
// Spec reference: http://www.w3.org/TR/css3-background/#ltpositiongt
+// Invariants:
+// - Always produces a four-value array on a successful parse.
+// - The values are: X edge, X offset, Y edge, Y offset.
+// - Edges are always keywords or null.
+// - A |center| edge will not have an offset.
bool
CSSParserImpl::ParsePositionValue(nsCSSValue& aOut)
{
RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(4);
aOut.SetArrayValue(value, eCSSUnit_Array);
// The following clarifies organisation of the array.
nsCSSValue &xEdge = value->Item(0),
@@ -12739,16 +12748,88 @@ CSSParserImpl::ParsePositionValue(nsCSSV
xOffset = yOffset;
yEdge = swapEdge;
yOffset = swapOffset;
}
return true;
}
+static void
+AdjustEdgeOffsetPairForBasicShape(nsCSSValue& aEdge,
+ nsCSSValue& aOffset,
+ uint8_t aDefaultEdge)
+{
+ // 0 length offsets are 0%
+ if (aOffset.IsLengthUnit() && aOffset.GetFloatValue() == 0.0) {
+ aOffset.SetPercentValue(0);
+ }
+
+ // Default edge is top/left in the 4-value case
+ // In case of 1 or 0 values, the default is center,
+ // but ParsePositionValue already handles this case
+ if (eCSSUnit_Null == aEdge.GetUnit()) {
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ }
+ // Default offset is 0%
+ if (eCSSUnit_Null == aOffset.GetUnit()) {
+ aOffset.SetPercentValue(0.0);
+ }
+ if (eCSSUnit_Enumerated == aEdge.GetUnit() &&
+ eCSSUnit_Percent == aOffset.GetUnit()) {
+ switch (aEdge.GetIntValue()) {
+ case NS_STYLE_IMAGELAYER_POSITION_CENTER:
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ MOZ_ASSERT(aOffset.GetPercentValue() == 0.0,
+ "center cannot be used with an offset");
+ aOffset.SetPercentValue(0.5);
+ break;
+ case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
+ MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_TOP);
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
+ break;
+ case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
+ MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_LEFT);
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
+ }
+ }
+}
+
+// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
+// We set values to defaults while parsing for basic shapes
+// Invariants:
+// - Always produces a four-value array on a successful parse.
+// - The values are: X edge, X offset, Y edge, Y offset
+// - Edges are always keywords (not including center)
+// - Offsets are nonnull
+// - Percentage offsets have keywords folded into them,
+// so "bottom 40%" or "right 20%" will not exist.
+bool
+CSSParserImpl::ParsePositionValueForBasicShape(nsCSSValue& aOut)
+{
+ if (!ParsePositionValue(aOut)) {
+ return false;
+ }
+ nsCSSValue::Array* value = aOut.GetArrayValue();
+ nsCSSValue& xEdge = value->Item(0);
+ nsCSSValue& xOffset = value->Item(1);
+ nsCSSValue& yEdge = value->Item(2);
+ nsCSSValue& yOffset = value->Item(3);
+ // A keyword edge + percent offset pair can be contracted
+ // into the percentage with the default value in the edge.
+ // Offset lengths which are 0 can also be rewritten as 0%
+ AdjustEdgeOffsetPairForBasicShape(xEdge, xOffset,
+ NS_STYLE_IMAGELAYER_POSITION_LEFT);
+ AdjustEdgeOffsetPairForBasicShape(yEdge, yOffset,
+ NS_STYLE_IMAGELAYER_POSITION_TOP);
+ return true;
+}
+
bool
CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY)
{
nsCSSValue scratch;
if (!ParsePositionValue(scratch)) {
return false;
}
@@ -15971,17 +16052,17 @@ CSSParserImpl::ParseCircleOrEllipseFunct
if (!ExpectSymbol(')', true)) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPositionEOF);
return false;
}
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsLiteral("at") ||
- !ParsePositionValue(position)) {
+ !ParsePositionValueForBasicShape(position)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
SkipUntil(')');
return false;
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -955,29 +955,78 @@ nsCSSValue::AppendCircleOrEllipseToStrin
if (hasRadii && aFunctionId == eCSSKeyword_ellipse) {
aResult.Append(' ');
AppendPositionCoordinateToString(array->Item(2), aProperty,
aResult, aSerialization);
}
}
+ if (hasRadii) {
+ aResult.Append(' ');
+ }
+
// Any position specified?
if (array->Item(count).GetUnit() != eCSSUnit_Array) {
MOZ_ASSERT(array->Item(count).GetUnit() == eCSSUnit_Null,
"unexpected value");
+ // We only serialize to the 2 or 4 value form
+ // |circle()| is valid, but should be expanded
+ // to |circle(at 50% 50%)|
+ aResult.AppendLiteral("at 50% 50%");
return;
}
- if (hasRadii) {
+ aResult.AppendLiteral("at ");
+ array->Item(count).AppendBasicShapePositionToString(aResult, aSerialization);
+}
+
+// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
+// basic-shape asks us to omit a lot of redundant things whilst serializing
+// position values. Other specs are not clear about this
+// (https://github.com/w3c/csswg-drafts/issues/368), so for now we special-case
+// basic shapes only
+void
+nsCSSValue::AppendBasicShapePositionToString(nsAString& aResult,
+ Serialization aSerialization) const
+{
+ const nsCSSValue::Array* array = GetArrayValue();
+ // We always parse these into an array of four elements
+ MOZ_ASSERT(array->Count() == 4,
+ "basic-shape position value doesn't have enough elements");
+
+ const nsCSSValue &xEdge = array->Item(0);
+ const nsCSSValue &xOffset = array->Item(1);
+ const nsCSSValue &yEdge = array->Item(2);
+ const nsCSSValue &yOffset = array->Item(3);
+
+ MOZ_ASSERT(xEdge.GetUnit() == eCSSUnit_Enumerated &&
+ yEdge.GetUnit() == eCSSUnit_Enumerated &&
+ xOffset.IsLengthPercentCalcUnit() &&
+ yOffset.IsLengthPercentCalcUnit() &&
+ xEdge.GetIntValue() != NS_STYLE_IMAGELAYER_POSITION_CENTER &&
+ yEdge.GetIntValue() != NS_STYLE_IMAGELAYER_POSITION_CENTER,
+ "Ensure invariants from ParsePositionValueBasicShape "
+ "haven't been modified");
+ if (xEdge.GetIntValue() == NS_STYLE_IMAGELAYER_POSITION_LEFT &&
+ yEdge.GetIntValue() == NS_STYLE_IMAGELAYER_POSITION_TOP) {
+ // We can omit these defaults
+ xOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
aResult.Append(' ');
+ yOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
+ } else {
+ // We only serialize to the two or four valued form
+ xEdge.AppendToString(eCSSProperty_object_position, aResult, aSerialization);
+ aResult.Append(' ');
+ xOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
+ aResult.Append(' ');
+ yEdge.AppendToString(eCSSProperty_object_position, aResult, aSerialization);
+ aResult.Append(' ');
+ yOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
}
- aResult.AppendLiteral("at ");
- array->Item(count).AppendToString(eCSSProperty_object_position,
- aResult, aSerialization);
}
// Helper to append |aString| with the shorthand sides notation used in e.g.
// 'padding'. |aProperties| and |aValues| are expected to have 4 elements.
/*static*/ void
nsCSSValue::AppendSidesShorthandToString(const nsCSSProperty aProperties[],
const nsCSSValue* aValues[],
nsAString& aString,
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -822,16 +822,19 @@ private:
void AppendPositionCoordinateToString(const nsCSSValue& aValue,
nsCSSProperty aProperty,
nsAString& aResult,
Serialization aSerialization) const;
void AppendCircleOrEllipseToString(
nsCSSKeyword aFunctionId,
nsCSSProperty aProperty, nsAString& aResult,
Serialization aValueSerialization) const;
+ void AppendBasicShapePositionToString(
+ nsAString& aResult,
+ Serialization aValueSerialization) const;
void AppendInsetToString(nsCSSProperty aProperty, nsAString& aResult,
Serialization aValueSerialization) const;
protected:
nsCSSUnit mUnit;
union {
int32_t mInt;
float mFloat;
// Note: the capacity of the buffer may exceed the length of the string.