Bug 1074522 - Implement ellipse()/circle() parsing and style computing. r=dbaron
authorDirk Schulze <dschulze@adobe.com>
Wed, 15 Oct 2014 00:03:00 +0200
changeset 210431 68308752b11b397a86ed6126dbce1566bb0f6c5e
parent 210430 01f454a80ce43d5b18ddc6b4c0414754affd4dc8
child 210432 e457165df2a3355453be2f9a0f06d485efdce821
push id50452
push usercbook@mozilla.com
push dateWed, 15 Oct 2014 08:24:25 +0000
treeherdermozilla-inbound@78f2a2439510 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs1074522
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1074522 - Implement ellipse()/circle() parsing and style computing. r=dbaron
dom/locales/en-US/chrome/layout/css.properties
layout/style/nsCSSParser.cpp
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSValue.cpp
layout/style/nsCSSValue.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleConsts.h
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -101,16 +101,19 @@ PEExpectedInt=Expected an integer but fo
 PEColorBadRGBContents=Expected number or percentage in rgb() but found '%1$S'.
 PEColorComponentBadTerm=Expected '%2$S' but found '%1$S'.
 PEColorHueEOF=hue
 PEExpectedComma=Expected ',' but found '%1$S'.
 PEColorSaturationEOF=saturation
 PEColorLightnessEOF=lightness
 PEColorOpacityEOF=opacity in color value
 PEExpectedNumber=Expected a number but found '%1$S'.
+PEPositionEOF=<position>
+PEExpectedPosition=Expected <position> but found '%1$S'.
+PEExpectedRadius=Expected radius but found '%1$S'.
 PEExpectedCloseParen=Expected ')' but found '%1$S'.
 PEDeclEndEOF=';' or '}' to end declaration
 PEParseDeclarationNoColon=Expected ':' but found '%1$S'.
 PEParseDeclarationDeclExpected=Expected declaration but found '%1$S'.
 PEEndOfDeclEOF=end of declaration
 PEImportantEOF=important
 PEExpectedImportant=Expected 'important' but found '%1$S'.
 PEBadDeclEnd=Expected ';' to terminate declaration but found '%1$S'.
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -960,16 +960,17 @@ protected:
   }
   bool IsParsingCompoundProperty(void) const {
     return mParsingCompoundProperty;
   }
 
   /* Functions for basic shapes */
   bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
   bool ParsePolygonFunction(nsCSSValue& aValue);
+  bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
 
   /* Functions for transform Parsing */
   bool ParseSingleTransform(bool aIsPrefixed, nsCSSValue& aValue);
   bool ParseFunction(nsCSSKeyword aFunction, const int32_t aAllowedTypes[],
                      int32_t aVariantMaskAll, uint16_t aMinElems,
                      uint16_t aMaxElems, nsCSSValue &aValue);
   bool ParseFunctionInternals(const int32_t aVariantMask[],
                               int32_t aVariantMaskAll,
@@ -13839,16 +13840,73 @@ CSSParserImpl::ParsePolygonFunction(nsCS
   if (numArgs > 1) {
     functionArray->Item(1) = fillRuleValue;
   }
 
   return true;
 }
 
 bool
+CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword,
+                                            nsCSSValue& aValue)
+{
+  nsCSSValue radiusX, radiusY, position;
+  bool hasRadius = false, hasPosition = false;
+
+  int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION |
+                 VARIANT_KEYWORD;
+  if (ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable)) {
+    if (aKeyword == eCSSKeyword_ellipse) {
+      if (!ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable)) {
+        REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
+        SkipUntil(')');
+        return false;
+      }
+    }
+    hasRadius = true;
+  }
+
+  if (!ExpectSymbol(')', true)) {
+    if (!GetToken(true)) {
+      REPORT_UNEXPECTED_EOF(PEPositionEOF);
+      return false;
+    }
+
+    if (mToken.mType != eCSSToken_Ident ||
+        !mToken.mIdent.LowerCaseEqualsLiteral("at") ||
+        !ParsePositionValue(position)) {
+      REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
+      SkipUntil(')');
+      return false;
+    }
+    if (!ExpectSymbol(')', true)) {
+      REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
+      SkipUntil(')');
+      return false;
+    }
+    hasPosition = true;
+  }
+
+  size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3;
+  nsRefPtr<nsCSSValue::Array> functionArray =
+    aValue.InitFunction(aKeyword, count);
+  if (hasRadius) {
+    functionArray->Item(1) = radiusX;
+    if (aKeyword == eCSSKeyword_ellipse) {
+      functionArray->Item(2) = radiusY;
+    }
+  }
+  if (hasPosition) {
+    functionArray->Item(count) = position;
+  }
+
+  return true;
+}
+
+bool
 CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
 {
   if (!GetToken(true)) {
     return false;
   }
 
   if (mToken.mType != eCSSToken_Function) {
     UngetToken();
@@ -13856,16 +13914,19 @@ CSSParserImpl::ParseBasicShape(nsCSSValu
   }
 
   // Specific shape function parsing always consumes tokens.
   *aConsumedTokens = true;
   nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
   switch (keyword) {
   case eCSSKeyword_polygon:
     return ParsePolygonFunction(aValue);
+  case eCSSKeyword_circle:
+  case eCSSKeyword_ellipse:
+    return ParseCircleOrEllipseFunction(keyword, aValue);
   default:
     return false;
   }
 }
 
 /* Parse a clip-path url to a <clipPath> element or a basic shape. */
 bool CSSParserImpl::ParseClipPath()
 {
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1886,16 +1886,22 @@ const KTableValue nsCSSProps::kClipShape
   eCSSKeyword_border_box,    NS_STYLE_CLIP_SHAPE_SIZING_BORDER,
   eCSSKeyword_margin_box,    NS_STYLE_CLIP_SHAPE_SIZING_MARGIN,
   eCSSKeyword_fill_box,      NS_STYLE_CLIP_SHAPE_SIZING_FILL,
   eCSSKeyword_stroke_box,    NS_STYLE_CLIP_SHAPE_SIZING_STROKE,
   eCSSKeyword_view_box,      NS_STYLE_CLIP_SHAPE_SIZING_VIEW,
   eCSSKeyword_UNKNOWN,-1
 };
 
+const KTableValue nsCSSProps::kShapeRadiusKTable[] = {
+  eCSSKeyword_closest_side, NS_RADIUS_CLOSEST_SIDE,
+  eCSSKeyword_farthest_side, NS_RADIUS_FARTHEST_SIDE,
+  eCSSKeyword_UNKNOWN, -1
+};
+
 const KTableValue nsCSSProps::kFilterFunctionKTable[] = {
   eCSSKeyword_blur, NS_STYLE_FILTER_BLUR,
   eCSSKeyword_brightness, NS_STYLE_FILTER_BRIGHTNESS,
   eCSSKeyword_contrast, NS_STYLE_FILTER_CONTRAST,
   eCSSKeyword_grayscale, NS_STYLE_FILTER_GRAYSCALE,
   eCSSKeyword_invert, NS_STYLE_FILTER_INVERT,
   eCSSKeyword_opacity, NS_STYLE_FILTER_OPACITY,
   eCSSKeyword_saturate, NS_STYLE_FILTER_SATURATE,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -532,16 +532,17 @@ public:
   static const KTableValue kBorderWidthKTable[];
   static const KTableValue kBoxAlignKTable[];
   static const KTableValue kBoxDecorationBreakKTable[];
   static const KTableValue kBoxDirectionKTable[];
   static const KTableValue kBoxOrientKTable[];
   static const KTableValue kBoxPackKTable[];
   static const KTableValue kClipShapeSizingKTable[];
   static const KTableValue kDominantBaselineKTable[];
+  static const KTableValue kShapeRadiusKTable[];
   static const KTableValue kFillRuleKTable[];
   static const KTableValue kFilterFunctionKTable[];
   static const KTableValue kImageRenderingKTable[];
   static const KTableValue kShapeRenderingKTable[];
   static const KTableValue kStrokeLinecapKTable[];
   static const KTableValue kStrokeLinejoinKTable[];
   static const KTableValue kStrokeContextValueKTable[];
   static const KTableValue kVectorEffectKTable[];
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -847,16 +847,66 @@ nsCSSValue::AppendPolygonToString(nsCSSP
                                                   nsCSSProps::kFillRuleKTable),
                        aResult);
     aResult.AppendLiteral(", ");
     ++index;
   }
   array->Item(index).AppendToString(aProperty, aResult, aSerialization);
 }
 
+inline void
+nsCSSValue::AppendPositionCoordinateToString(
+                const nsCSSValue& aValue, nsCSSProperty aProperty,
+                nsAString& aResult, Serialization aSerialization) const
+{
+  if (aValue.GetUnit() == eCSSUnit_Enumerated) {
+    int32_t intValue = aValue.GetIntValue();
+    AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+                          nsCSSProps::kShapeRadiusKTable), aResult);
+  } else {
+    aValue.AppendToString(aProperty, aResult, aSerialization);
+  }
+}
+
+void
+nsCSSValue::AppendCircleOrEllipseToString(nsCSSKeyword aFunctionId,
+                                          nsCSSProperty aProperty,
+                                          nsAString& aResult,
+                                          Serialization aSerialization) const
+{
+  const nsCSSValue::Array* array = GetArrayValue();
+  size_t count = aFunctionId == eCSSKeyword_circle ? 2 : 3;
+  NS_ABORT_IF_FALSE(array->Count() == count + 1, "wrong number of arguments");
+
+  bool hasRadii = array->Item(1).GetUnit() != eCSSUnit_Null;
+
+  AppendPositionCoordinateToString(array->Item(1), aProperty,
+                                     aResult, aSerialization);
+
+  if (hasRadii && aFunctionId == eCSSKeyword_ellipse) {
+    aResult.Append(' ');
+    AppendPositionCoordinateToString(array->Item(2), aProperty,
+                                     aResult, aSerialization);
+  }
+
+  // Any position specified?
+  if (array->Item(count).GetUnit() != eCSSUnit_Array) {
+    NS_ABORT_IF_FALSE(array->Item(count).GetUnit() == eCSSUnit_Null,
+                      "unexpected value");
+    return;
+  }
+
+  if (hasRadii) {
+    aResult.Append(' ');
+  }
+  aResult.AppendLiteral("at ");
+  array->Item(count).AppendToString(eCSSProperty_background_position,
+                                    aResult, aSerialization);
+}
+
 void
 nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult,
                            Serialization aSerialization) const
 {
   // eCSSProperty_UNKNOWN gets used for some recursive calls below.
   NS_ABORT_IF_FALSE((0 <= aProperty &&
                      aProperty <= eCSSProperty_COUNT_no_shorthands) ||
                     aProperty == eCSSProperty_UNKNOWN,
@@ -986,16 +1036,22 @@ nsCSSValue::AppendToString(nsCSSProperty
     nsStyleUtil::AppendEscapedCSSIdent(ident, aResult);
     aResult.Append('(');
 
     switch (functionId) {
       case eCSSKeyword_polygon:
         AppendPolygonToString(aProperty, aResult, aSerialization);
         break;
 
+      case eCSSKeyword_circle:
+      case eCSSKeyword_ellipse:
+        AppendCircleOrEllipseToString(functionId, aProperty, aResult,
+                                      aSerialization);
+        break;
+
       default: {
         // Now, step through the function contents, writing each of
         // them as we go.
         for (size_t index = 1; index < array->Count(); ++index) {
           array->Item(index).AppendToString(aProperty, aResult,
                                             aSerialization);
 
           /* If we're not at the final element, append a comma. */
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -716,17 +716,24 @@ public:
 
 private:
   static const char16_t* GetBufferValue(nsStringBuffer* aBuffer) {
     return static_cast<char16_t*>(aBuffer->Data());
   }
 
   void AppendPolygonToString(nsCSSProperty aProperty, nsAString& aResult,
                              Serialization aValueSerialization) const;
-
+  void AppendPositionCoordinateToString(const nsCSSValue& aValue,
+                                        nsCSSProperty aProperty,
+                                        nsAString& aResult,
+                                        Serialization aSerialization) const;
+  void AppendCircleOrEllipseToString(
+           nsCSSKeyword aFunctionId,
+           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.
     // If we're of a string type, mString is not null.
     nsStringBuffer* mString;
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5170,51 +5170,103 @@ nsComputedDOMStyle::DoGetLightingColor()
 CSSValue*
 nsComputedDOMStyle::DoGetStopColor()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
   SetToRGBAColor(val, StyleSVGReset()->mStopColor);
   return val;
 }
 
+inline void AppendBasicShapeTypeToString(nsStyleBasicShape::Type aType,
+                                         nsAutoString& aString)
+{
+  nsCSSKeyword functionName;
+  switch (aType) {
+    case nsStyleBasicShape::Type::ePolygon:
+      functionName = eCSSKeyword_polygon;
+      break;
+    case nsStyleBasicShape::Type::eCircle:
+      functionName = eCSSKeyword_circle;
+      break;
+    case nsStyleBasicShape::Type::eEllipse:
+      functionName = eCSSKeyword_ellipse;
+      break;
+    default:
+      NS_NOTREACHED("unexpected type");
+  }
+  AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(functionName),
+                     aString);
+}
+
 CSSValue*
 nsComputedDOMStyle::CreatePrimitiveValueForClipPath(
   const nsStyleBasicShape* aStyleBasicShape, uint8_t aSizingBox)
 {
   nsDOMCSSValueList* valueList = GetROCSSValueList(false);
-
-  if (aStyleBasicShape &&
-      aStyleBasicShape->GetShapeType() == nsStyleBasicShape::Type::ePolygon) {
+  if (aStyleBasicShape) {
+    nsStyleBasicShape::Type type = aStyleBasicShape->GetShapeType();
     // Shape function name and opening parenthesis.
     nsAutoString shapeFunctionString;
-    AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(eCSSKeyword_polygon),
-                       shapeFunctionString);
+    AppendBasicShapeTypeToString(type, shapeFunctionString);
     shapeFunctionString.Append('(');
-    bool hasEvenOdd = aStyleBasicShape->GetFillRule() ==
-                          NS_STYLE_FILL_RULE_EVENODD;
-    if (hasEvenOdd) {
-      shapeFunctionString.AppendLiteral("evenodd");
-    }
-    for (size_t i = 0; i < aStyleBasicShape->Coordinates().Length(); i += 2) {
-      nsAutoString coordString;
-      if (i > 0 || hasEvenOdd) {
-        shapeFunctionString.AppendLiteral(", ");
+    switch (type) {
+      case nsStyleBasicShape::Type::ePolygon: {
+        bool hasEvenOdd = aStyleBasicShape->GetFillRule() ==
+                              NS_STYLE_FILL_RULE_EVENODD;
+        if (hasEvenOdd) {
+          shapeFunctionString.AppendLiteral("evenodd");
+        }
+        for (size_t i = 0;
+             i < aStyleBasicShape->Coordinates().Length(); i += 2) {
+          nsAutoString coordString;
+          if (i > 0 || hasEvenOdd) {
+            shapeFunctionString.AppendLiteral(", ");
+          }
+          SetCssTextToCoord(coordString,
+                            aStyleBasicShape->Coordinates()[i]);
+          shapeFunctionString.Append(coordString);
+          shapeFunctionString.Append(' ');
+          SetCssTextToCoord(coordString,
+                            aStyleBasicShape->Coordinates()[i + 1]);
+          shapeFunctionString.Append(coordString);
+        }
+        break;
       }
-      SetCssTextToCoord(coordString,
-                        aStyleBasicShape->Coordinates()[i]);
-      shapeFunctionString.Append(coordString);
-      shapeFunctionString.Append(' ');
-      SetCssTextToCoord(coordString,
-                        aStyleBasicShape->Coordinates()[i + 1]);
-      shapeFunctionString.Append(coordString);
+      case nsStyleBasicShape::Type::eCircle:
+      case nsStyleBasicShape::Type::eEllipse: {
+        const nsTArray<nsStyleCoord>& radii = aStyleBasicShape->Coordinates();
+        NS_ABORT_IF_FALSE(radii.Length() ==
+                          (nsStyleBasicShape::Type::eCircle ? 1 : 2),
+                          "wrong number of radii");
+        for (size_t i = 0; i < radii.Length(); ++i) {
+          nsAutoString radius;
+          nsRefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+          bool clampNegativeCalc = true;
+          SetValueToCoord(value, radii[i], clampNegativeCalc, nullptr,
+                          nsCSSProps::kShapeRadiusKTable);
+          value->GetCssText(radius);
+          shapeFunctionString.Append(radius);
+          shapeFunctionString.Append(' ');
+        }
+        shapeFunctionString.AppendLiteral("at ");
+
+        nsRefPtr<nsDOMCSSValueList> position = GetROCSSValueList(false);
+        nsAutoString positionString;
+        SetValueToPosition(aStyleBasicShape->GetPosition(), position);
+        position->GetCssText(positionString);
+        shapeFunctionString.Append(positionString);
+        break;
+      }
+      default:
+        NS_NOTREACHED("unexpected type");
     }
     shapeFunctionString.Append(')');
-    nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue;
-    val->SetString(shapeFunctionString);
-    valueList->AppendCSSValue(val);
+    nsROCSSPrimitiveValue* functionValue = new nsROCSSPrimitiveValue;
+    functionValue->SetString(shapeFunctionString);
+    valueList->AppendCSSValue(functionValue);
   }
 
   if (aSizingBox == NS_STYLE_CLIP_SHAPE_SIZING_NOBOX) {
     return valueList;
   }
 
   nsAutoString boxString;
   AppendASCIItoUTF16(
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -8796,16 +8796,17 @@ nsRuleNode::SetStyleClipPathToCSSValue(n
     if (!cur) {
       break;
     }
     if (cur->mValue.GetUnit() == eCSSUnit_Function) {
       nsCSSValue::Array* shapeFunction = cur->mValue.GetArrayValue();
       nsCSSKeyword functionName =
         (nsCSSKeyword)shapeFunction->Item(0).GetIntValue();
       if (functionName == eCSSKeyword_polygon) {
+        NS_ABORT_IF_FALSE(!basicShape, "did not expect value");
         basicShape = new nsStyleBasicShape(nsStyleBasicShape::ePolygon);
         NS_ABORT_IF_FALSE(shapeFunction->Count() > 1,
                           "polygon has wrong number of arguments");
         size_t j = 1;
         if (shapeFunction->Item(j).GetUnit() == eCSSUnit_Enumerated) {
           basicShape->SetFillRule(shapeFunction->Item(j).GetIntValue());
           ++j;
         }
@@ -8825,16 +8826,56 @@ nsRuleNode::SetStyleClipPathToCSSValue(n
           DebugOnly<bool> didSetCoordY = SetCoord(curPair->mYValue, yCoord,
                                                   nsStyleCoord(), mask,
                                                   aStyleContext, aPresContext,
                                                   aCanStoreInRuleTree);
           coordinates.AppendElement(yCoord);
           NS_ABORT_IF_FALSE(didSetCoordY, "unexpected y coordinate unit");
           curPair = curPair->mNext;
         }
+      } else if (functionName == eCSSKeyword_circle ||
+                 functionName == eCSSKeyword_ellipse) {
+        nsStyleBasicShape::Type type = functionName == eCSSKeyword_circle ?
+                                       nsStyleBasicShape::eCircle :
+                                       nsStyleBasicShape::eEllipse;
+        NS_ABORT_IF_FALSE(!basicShape, "did not expect value");
+        basicShape = new nsStyleBasicShape(type);
+        int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
+                       SETCOORD_STORE_CALC | SETCOORD_ENUMERATED;
+        size_t count = type == nsStyleBasicShape::eCircle ? 2 : 3;
+        NS_ABORT_IF_FALSE(shapeFunction->Count() == count + 1,
+                          "unexpected arguments count");
+        NS_ABORT_IF_FALSE(type == nsStyleBasicShape::eCircle ||
+                        (shapeFunction->Item(1).GetUnit() == eCSSUnit_Null) ==
+                        (shapeFunction->Item(2).GetUnit() == eCSSUnit_Null),
+                        "ellipse should have two radii or none");
+        for (size_t j = 1; j < count; ++j) {
+          const nsCSSValue& val = shapeFunction->Item(j);
+          nsStyleCoord radius;
+          if (val.GetUnit() != eCSSUnit_Null) {
+            DebugOnly<bool> didSetRadius = SetCoord(val, radius,
+                                                    nsStyleCoord(), mask,
+                                                    aStyleContext,
+                                                    aPresContext,
+                                                    aCanStoreInRuleTree);
+            NS_ABORT_IF_FALSE(didSetRadius, "unexpected radius unit");
+          } else {
+            radius.SetIntValue(NS_RADIUS_CLOSEST_SIDE, eStyleUnit_Enumerated);
+          }
+          basicShape->Coordinates().AppendElement(radius);
+        }
+        const nsCSSValue& positionVal = shapeFunction->Item(count);
+        if (positionVal.GetUnit() == eCSSUnit_Array) {
+          ComputePositionValue(aStyleContext, positionVal,
+                               basicShape->GetPosition(),
+                               aCanStoreInRuleTree);
+        } else {
+            NS_ABORT_IF_FALSE(positionVal.GetUnit() == eCSSUnit_Null,
+                              "expected no value");
+        }
       } else {
         // XXX Handle more basic shape functions later.
         NS_NOTREACHED("unexpected basic shape function");
         return;
       }
     } else if (cur->mValue.GetUnit() == eCSSUnit_Enumerated) {
       int32_t type = cur->mValue.GetIntValue();
       if (type > NS_STYLE_CLIP_SHAPE_SIZING_VIEW ||
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -72,18 +72,18 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_CLIP_SHAPE_SIZING_BORDER  3
 #define NS_STYLE_CLIP_SHAPE_SIZING_MARGIN  4
 #define NS_STYLE_CLIP_SHAPE_SIZING_FILL    5
 #define NS_STYLE_CLIP_SHAPE_SIZING_STROKE  6
 #define NS_STYLE_CLIP_SHAPE_SIZING_VIEW    7
 
 // Basic Shapes
 #define NS_STYLE_BASIC_SHAPE_POLYGON       0
-//#define NS_STYLE_BASIC_SHAPE_CIRCLE      1
-//#define NS_STYLE_BASIC_SHAPE_ELLIPSE     2
+#define NS_STYLE_BASIC_SHAPE_CIRCLE        1
+#define NS_STYLE_BASIC_SHAPE_ELLIPSE       2
 //#define NS_STYLE_BASIC_SHAPE_INSET       3
 
 // box-shadow
 #define NS_STYLE_BOX_SHADOW_INSET         0
 
 // float-edge
 #define NS_STYLE_FLOAT_EDGE_CONTENT       0
 #define NS_STYLE_FLOAT_EDGE_MARGIN        1
@@ -150,16 +150,19 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_BOX_ORIENT_HORIZONTAL 0
 #define NS_STYLE_BOX_ORIENT_VERTICAL   1
 
 // orient
 #define NS_STYLE_ORIENT_HORIZONTAL 0
 #define NS_STYLE_ORIENT_VERTICAL   1
 #define NS_STYLE_ORIENT_AUTO       2
 
+#define NS_RADIUS_FARTHEST_SIDE 0
+#define NS_RADIUS_CLOSEST_SIDE  1
+
 // stack-sizing
 #define NS_STYLE_STACK_SIZING_IGNORE         0
 #define NS_STYLE_STACK_SIZING_STRETCH_TO_FIT 1
 
 // Azimuth - See nsStyleAural
 #define NS_STYLE_AZIMUTH_LEFT_SIDE        0x00
 #define NS_STYLE_AZIMUTH_FAR_LEFT         0x01
 #define NS_STYLE_AZIMUTH_LEFT             0x02
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2826,65 +2826,86 @@ struct nsStyleSVG {
     return mFill.mType != eStyleSVGPaintType_None && mFillOpacity > 0;
   }
 };
 
 class nsStyleBasicShape MOZ_FINAL {
 public:
   enum Type {
     // eInset,
-    // eCircle,
-    // eEllipse,
+    eCircle,
+    eEllipse,
     ePolygon
   };
 
   explicit nsStyleBasicShape(Type type)
     : mType(type)
   {
+    mPosition.SetInitialPercentValues(0.5f);
   }
 
   Type GetShapeType() const { return mType; }
 
   int32_t GetFillRule() const { return mFillRule; }
   void SetFillRule(int32_t aFillRule)
   {
     NS_ASSERTION(mType == ePolygon, "expected polygon");
     mFillRule = aFillRule;
   }
 
+  typedef nsStyleBackground::Position Position;
+  Position& GetPosition() {
+    NS_ASSERTION(mType == eCircle || mType == eEllipse,
+                 "expected circle or ellipse");
+    return mPosition;
+  }
+  const Position& GetPosition() const {
+    NS_ASSERTION(mType == eCircle || mType == eEllipse,
+                 "expected circle or ellipse");
+    return mPosition;
+  }
+
+  // mCoordinates has coordinates for polygon or radii for
+  // ellipse and circle.
   nsTArray<nsStyleCoord>& Coordinates()
   {
-    NS_ASSERTION(mType == ePolygon, "expected polygon");
+    NS_ASSERTION(mType == ePolygon || mType == eCircle || mType == eEllipse,
+                 "expected polygon, circle or ellipse");
     return mCoordinates;
   }
 
   const nsTArray<nsStyleCoord>& Coordinates() const
   {
-    NS_ASSERTION(mType == ePolygon, "expected polygon");
+    NS_ASSERTION(mType == ePolygon || mType == eCircle || mType == eEllipse,
+                 "expected polygon, circle or ellipse");
     return mCoordinates;
   }
 
   bool operator==(const nsStyleBasicShape& aOther) const
   {
     return mType == aOther.mType &&
            mFillRule == aOther.mFillRule &&
-           mCoordinates == aOther.mCoordinates;
+           mCoordinates == aOther.mCoordinates &&
+           mPosition == aOther.mPosition;
   }
   bool operator!=(const nsStyleBasicShape& aOther) const {
     return !(*this == aOther);
   }
 
   NS_INLINE_DECL_REFCOUNTING(nsStyleBasicShape);
 
 private:
   ~nsStyleBasicShape() {}
 
   Type mType;
   int32_t mFillRule;
+  // mCoordinates has coordinates for polygon or radii for
+  // ellipse and circle.
   nsTArray<nsStyleCoord> mCoordinates;
+  Position mPosition;
 };
 
 struct nsStyleClipPath
 {
   nsStyleClipPath();
   nsStyleClipPath(const nsStyleClipPath& aSource);
   ~nsStyleClipPath();
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -4617,16 +4617,49 @@ if (SpecialPowers.getBoolPref("layout.cs
       "padding-box    polygon(   0  20px ,  30px    20% )  ",
       "polygon(evenodd, 20% 20em) content-box",
       "polygon(evenodd, 20vh 20em) padding-box",
       "polygon(evenodd, 20vh calc(20% + 20em)) border-box",
       "polygon(evenodd, 20vh 20vw) margin-box",
       "polygon(evenodd, 20pt 20cm) fill-box",
       "polygon(evenodd, 20ex 20pc) stroke-box",
       "polygon(evenodd, 20rem 20in) view-box",
+
+      "circle()",
+      "circle(at center)",
+      "circle(at top left 20px)",
+      "circle(at bottom right)",
+      "circle(20%)",
+      "circle(300px)",
+      "circle(calc(20px + 30px))",
+      "circle(farthest-side)",
+      "circle(closest-side)",
+      "circle(closest-side at center)",
+      "circle(farthest-side at top)",
+      "circle(20px at top right)",
+      "circle(40% at 50% 100%)",
+      "circle(calc(20% + 20%) at right bottom)",
+      "circle() padding-box",
+
+      "ellipse()",
+      "ellipse(at center)",
+      "ellipse(at top left 20px)",
+      "ellipse(at bottom right)",
+      "ellipse(20% 20%)",
+      "ellipse(300px 50%)",
+      "ellipse(calc(20px + 30px) 10%)",
+      "ellipse(farthest-side closest-side)",
+      "ellipse(closest-side farthest-side)",
+      "ellipse(farthest-side farthest-side)",
+      "ellipse(closest-side closest-side)",
+      "ellipse(closest-side closest-side at center)",
+      "ellipse(20% farthest-side at top)",
+      "ellipse(20px 50% at top right)",
+      "ellipse(closest-side 40% at 50% 100%)",
+      "ellipse(calc(20% + 20%) calc(20px + 20cm) at right bottom)",
     ],
     invalid_values: [
       "url(#test) url(#tes2)",
       "polygon (0 0)",
       "polygon(20px, 40px)",
       "border-box content-box",
       "polygon(0 0) polygon(0 0)",
       "polygon(nonzero 0 0)",
@@ -4651,21 +4684,57 @@ if (SpecialPowers.getBoolPref("layout.cs
       "polygon(0 0) conten-box",
       "polygon(0 0) polygon(0 0) farthest-corner",
       "polygon(0 0) polygon(0 0) polygon(0 0)",
       "border-box polygon(0, 0)",
       "border-box padding-box",
       "margin-box farthest-side",
       "nonsense() border-box",
       "border-box nonsense()",
+
+      "circle(at)",
+      "circle(at 20% 20% 30%)",
+      "circle(20px 2px at center)",
+      "circle(2at center)",
+      "circle(closest-corner)",
+      "circle(at center top closest-side)",
+      "circle(-20px)",
+      "circle(farthest-side closest-side)",
+      "circle(20% 20%)",
+      "circle(at farthest-side)",
+
+      "ellipse(at)",
+      "ellipse(at 20% 20% 30%)",
+      "ellipse(20px at center)",
+      "ellipse(-20px 20px)",
+      "ellipse(closest-corner farthest-corner)",
+      "ellipse(20px -20px)",
+      "ellipse(-20px -20px)",
+      "ellipse(farthest-side)",
+      "ellipse(20%)",
+      "ellipse(at farthest-side farthest-side)",
+
+      "polygon(at)",
+      "polygon(at 20% 20% 30%)",
+      "polygon(20px at center)",
+      "polygon(2px 2at center)",
+      "polygon(closest-corner farthest-corner)",
+      "polygon(at center top closest-side closest-side)",
+      "polygon(40% at 50% 100%)",
+      "polygon(40% farthest-side 20px at 50% 100%)",
     ],
     unbalanced_values: [
       "polygon(30% 30%",
       "polygon(nonzero, 20% 20px",
       "polygon(evenodd, 20px 20px",
+
+      "circle(",
+      "circle(40% at 50% 100%",
+      "ellipse(",
+      "ellipse(40% at 50% 100%",
     ]
   };
 }
 
 
 if (SpecialPowers.getBoolPref("layout.css.filters.enabled")) {
   gCSSProperties["filter"] = {
     domProp: "filter",