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 210508 68308752b11b397a86ed6126dbce1566bb0f6c5e
parent 210507 01f454a80ce43d5b18ddc6b4c0414754affd4dc8
child 210509 e457165df2a3355453be2f9a0f06d485efdce821
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersdbaron
bugs1074522
milestone36.0a1
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",