Bug 1098939 Part 1 - Add shape-outside support to style system. draft
authorTing-Yu Lin <tlin@mozilla.com>
Mon, 23 May 2016 01:03:25 +0800
changeset 369619 d9fd7a942b5ee0fa6062500cc25de58d1a43537f
parent 369355 3efb130c6dca2cd2899f0e99befb546ad8cfc241
child 521574 43f3e1e762c56947a19565d6f86ac01fda7ff109
push id18866
push userbmo:tlin@mozilla.com
push dateMon, 23 May 2016 07:38:35 +0000
bugs1098939
milestone49.0a1
Bug 1098939 Part 1 - Add shape-outside support to style system. MozReview-Commit-ID: 1KZS299CFul
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsRuleNode.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
modules/libpref/init/all.js
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1058,16 +1058,17 @@ protected:
   bool ParseTextEmphasisPosition(nsCSSValue& aValue);
   bool ParseTextEmphasisStyle(nsCSSValue& aValue);
   bool ParseTextCombineUpright(nsCSSValue& aValue);
   bool ParseTextOverflow(nsCSSValue& aValue);
   bool ParseTouchAction(nsCSSValue& aValue);
 
   bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow);
   bool ParseShadowList(nsCSSProperty aProperty);
+  bool ParseShapeOutside(nsCSSValue& aValue);
   bool ParseTransitionProperty();
   bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
   bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
                                                      char aStop,
                                                      bool aCheckRange);
   bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
   enum ParseAnimationOrTransitionShorthandResult {
     eParseAnimationOrTransitionShorthand_Values,
@@ -11705,16 +11706,18 @@ CSSParserImpl::ParseSingleValuePropertyB
     case eCSSProperty_scroll_snap_points_x:
       return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x);
     case eCSSProperty_scroll_snap_points_y:
       return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_y);
     case eCSSProperty_scroll_snap_destination:
       return ParseScrollSnapDestination(aValue);
     case eCSSProperty_scroll_snap_coordinate:
       return ParseScrollSnapCoordinate(aValue);
+    case eCSSProperty_shape_outside:
+      return ParseShapeOutside(aValue);
     case eCSSProperty_text_align:
       return ParseTextAlign(aValue);
     case eCSSProperty_text_align_last:
       return ParseTextAlignLast(aValue);
     case eCSSProperty_text_decoration_line:
       return ParseTextDecorationLine(aValue);
     case eCSSProperty_text_combine_upright:
       return ParseTextCombineUpright(aValue);
@@ -16069,16 +16072,62 @@ bool CSSParserImpl::ParseClipPath()
 
     value.SetArrayValue(fullValue, eCSSUnit_Array);
   }
 
   AppendValue(eCSSProperty_clip_path, value);
   return true;
 }
 
+// none | [ <basic-shape> || <shape-box> ] | <image>
+bool
+CSSParserImpl::ParseShapeOutside(nsCSSValue& aValue)
+{
+  if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
+    // 'inherit', 'initial', 'unset', 'none', and <image> url must be alone.
+    return true;
+  }
+
+  nsCSSValue shapeBox;
+  bool hasBox = ParseEnum(shapeBox, nsCSSProps::kShapeBoxKTable);
+  const bool boxCameFirst = hasBox;
+
+  nsCSSValue basicShape;
+  bool basicShapeConsumedTokens = false;
+  bool hasShape = ParseBasicShape(basicShape, &basicShapeConsumedTokens);
+
+  // Parsing wasn't successful if ParseBasicShape() consumed tokens but failed
+  // or if the token was neither a shape-box nor a basic-shape.
+  if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
+    return false;
+  }
+
+  // Check if the second argument is a shape box if the first wasn't.
+  if (!hasBox) {
+    hasBox = ParseEnum(shapeBox, nsCSSProps::kClipShapeSizingKTable);
+  }
+
+  RefPtr<nsCSSValue::Array> fullValue =
+    nsCSSValue::Array::Create(hasBox && hasShape ? 2 : 1);
+
+  if (hasBox && hasShape) {
+    fullValue->Item(boxCameFirst ? 0 : 1) = shapeBox;
+    fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
+  } else if (hasBox) {
+    fullValue->Item(0) = shapeBox;
+  } else {
+    MOZ_ASSERT(hasShape, "Should've bailed if we got neither box nor shape!");
+    fullValue->Item(0) = basicShape;
+  }
+
+  aValue.SetArrayValue(fullValue, eCSSUnit_Array);
+
+  return true;
+}
+
 bool CSSParserImpl::ParseTransformOrigin(bool aPerspective)
 {
   nsCSSValuePair position;
   if (!ParseBoxPositionValues(position, true))
     return false;
 
   nsCSSProperty prop = eCSSProperty_transform_origin;
   if (aPerspective) {
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -3692,16 +3692,27 @@ CSS_PROP_DISPLAY(
     scroll_snap_type_y,
     ScrollSnapTypeY,
     CSS_PROPERTY_PARSE_VALUE,
     "layout.css.scroll-snap.enabled",
     VARIANT_HK,
     kScrollSnapTypeKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+    shape-outside,
+    shape_outside,
+    ShapeOutside,
+    CSS_PROPERTY_PARSE_VALUE |
+        CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+    "layout.css.shape-outside.enabled",
+    0,
+    nullptr,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_None)
 CSS_PROP_SVG(
     shape-rendering,
     shape_rendering,
     ShapeRendering,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kShapeRenderingKTable,
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2331,16 +2331,24 @@ const KTableEntry nsCSSProps::kImageRend
 };
 
 const KTableEntry nsCSSProps::kMaskTypeKTable[] = {
   { eCSSKeyword_luminance, NS_STYLE_MASK_TYPE_LUMINANCE },
   { eCSSKeyword_alpha, NS_STYLE_MASK_TYPE_ALPHA },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
+const KTableEntry nsCSSProps::kShapeBoxKTable[] = {
+  { eCSSKeyword_margin_box, uint8_t(StyleShapeBox::Margin) },
+  { eCSSKeyword_border_box, uint8_t(StyleShapeBox::Border) },
+  { eCSSKeyword_padding_box, uint8_t(StyleShapeBox::Padding) },
+  { eCSSKeyword_content_box, uint8_t(StyleShapeBox::Content) },
+  { eCSSKeyword_UNKNOWN, -1 }
+};
+
 const KTableEntry nsCSSProps::kShapeRenderingKTable[] = {
   { eCSSKeyword_auto, NS_STYLE_SHAPE_RENDERING_AUTO },
   { eCSSKeyword_optimizespeed, NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED },
   { eCSSKeyword_crispedges, NS_STYLE_SHAPE_RENDERING_CRISPEDGES },
   { eCSSKeyword_geometricprecision, NS_STYLE_SHAPE_RENDERING_GEOMETRICPRECISION },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -679,16 +679,17 @@ public:
   static const KTableEntry kCounterSpeakAsKTable[];
   static const KTableEntry kCounterSymbolsSystemKTable[];
   static const KTableEntry kCounterSystemKTable[];
   static const KTableEntry kDominantBaselineKTable[];
   static const KTableEntry kShapeRadiusKTable[];
   static const KTableEntry kFillRuleKTable[];
   static const KTableEntry kFilterFunctionKTable[];
   static const KTableEntry kImageRenderingKTable[];
+  static const KTableEntry kShapeBoxKTable[];
   static const KTableEntry kShapeRenderingKTable[];
   static const KTableEntry kStrokeLinecapKTable[];
   static const KTableEntry kStrokeLinejoinKTable[];
   static const KTableEntry kStrokeContextValueKTable[];
   static const KTableEntry kVectorEffectKTable[];
   static const KTableEntry kTextAnchorKTable[];
   static const KTableEntry kTextRenderingKTable[];
   static const KTableEntry kColorAdjustKTable[];
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3197,16 +3197,68 @@ nsComputedDOMStyle::DoGetScrollSnapCoord
       SetValueToPosition(sd->mScrollSnapCoordinate[i], itemList);
       valueList->AppendCSSValue(itemList.forget());
     }
     return valueList.forget();
   }
 }
 
 already_AddRefed<CSSValue>
+nsComputedDOMStyle::CreatePrimitiveValueForShapeOutside(
+  const nsStyleBasicShape* aStyleBasicShape, StyleShapeBox aShapeBox)
+{
+  RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+  if (aStyleBasicShape) {
+    valueList->AppendCSSValue(
+      CreatePrimitiveValueForBasicShape(aStyleBasicShape));
+  }
+
+  if (aShapeBox == StyleShapeBox::None) {
+    return valueList.forget();
+  }
+
+  nsAutoString boxString;
+  AppendASCIItoUTF16(
+    nsCSSProps::ValueToKeyword(static_cast<uint8_t>(aShapeBox),
+                               nsCSSProps::kShapeBoxKTable),
+                               boxString);
+  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+  val->SetString(boxString);
+  valueList->AppendCSSValue(val.forget());
+
+  return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetShapeOutside()
+{
+  const nsStyleDisplay* display = StyleDisplay();
+  switch (display->mShapeOutside.GetType()) {
+    case StyleShapeOutside::Type::None: {
+      RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+      val->SetIdent(eCSSKeyword_none);
+      return val.forget();
+    }
+    case StyleShapeOutside::Type::ShapeBox:
+      return CreatePrimitiveValueForShapeOutside(
+        nullptr, display->mShapeOutside.GetShapeBox());
+    case StyleShapeOutside::Type::BasicShape:
+      return CreatePrimitiveValueForShapeOutside(
+        display->mShapeOutside.GetBasicShape(),
+        display->mShapeOutside.GetShapeBox());
+    case StyleShapeOutside::Type::URL: {
+      RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+      val->SetIdent(eCSSKeyword_none);
+      return val.forget();
+    }
+  }
+  return nullptr;
+}
+
+already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetOutlineWidth()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
 
   const nsStyleOutline* outline = StyleOutline();
 
   nscoord width;
   if (outline->GetOutlineStyle() == NS_STYLE_BORDER_STYLE_NONE) {
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -476,16 +476,17 @@ private:
   already_AddRefed<CSSValue> DoGetScrollBehavior();
   already_AddRefed<CSSValue> DoGetScrollSnapType();
   already_AddRefed<CSSValue> DoGetScrollSnapTypeX();
   already_AddRefed<CSSValue> DoGetScrollSnapTypeY();
   already_AddRefed<CSSValue> DoGetScrollSnapPointsX();
   already_AddRefed<CSSValue> DoGetScrollSnapPointsY();
   already_AddRefed<CSSValue> DoGetScrollSnapDestination();
   already_AddRefed<CSSValue> DoGetScrollSnapCoordinate();
+  already_AddRefed<CSSValue> DoGetShapeOutside();
 
   /* User interface properties */
   already_AddRefed<CSSValue> DoGetCursor();
   already_AddRefed<CSSValue> DoGetForceBrokenImageIcon();
   already_AddRefed<CSSValue> DoGetIMEMode();
   already_AddRefed<CSSValue> DoGetUserFocus();
   already_AddRefed<CSSValue> DoGetUserInput();
   already_AddRefed<CSSValue> DoGetUserModify();
@@ -640,16 +641,18 @@ private:
 
   /* Helper functions for computing the filter property style. */
   void SetCssTextToCoord(nsAString& aCssText, const nsStyleCoord& aCoord);
   already_AddRefed<CSSValue> CreatePrimitiveValueForStyleFilter(
     const nsStyleFilter& aStyleFilter);
 
   already_AddRefed<CSSValue> CreatePrimitiveValueForClipPath(
     const nsStyleBasicShape* aStyleBasicShape, uint8_t aSizingBox);
+  already_AddRefed<CSSValue> CreatePrimitiveValueForShapeOutside(
+    const nsStyleBasicShape* aStyleBasicShape, mozilla::StyleShapeBox aShapeBox);
 
   // Helper function for computing basic shape styles.
   already_AddRefed<CSSValue> CreatePrimitiveValueForBasicShape(
     const nsStyleBasicShape* aStyleBasicShape);
   void BoxValuesToString(nsAString& aString,
                          const nsTArray<nsStyleCoord>& aBoxValues);
   void BasicShapeRadiiToString(nsAString& aCssText,
                                const nsStyleCorners& aCorners);
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -210,16 +210,17 @@ COMPUTED_STYLE_PROP(ruby_align,         
 COMPUTED_STYLE_PROP(ruby_position,                 RubyPosition)
 COMPUTED_STYLE_PROP(scroll_behavior,               ScrollBehavior)
 COMPUTED_STYLE_PROP(scroll_snap_coordinate,        ScrollSnapCoordinate)
 COMPUTED_STYLE_PROP(scroll_snap_destination,       ScrollSnapDestination)
 COMPUTED_STYLE_PROP(scroll_snap_points_x,          ScrollSnapPointsX)
 COMPUTED_STYLE_PROP(scroll_snap_points_y,          ScrollSnapPointsY)
 COMPUTED_STYLE_PROP(scroll_snap_type_x,            ScrollSnapTypeX)
 COMPUTED_STYLE_PROP(scroll_snap_type_y,            ScrollSnapTypeY)
+COMPUTED_STYLE_PROP(shape_outside,                 ShapeOutside)
 //// COMPUTED_STYLE_PROP(size,                     Size)
 COMPUTED_STYLE_PROP(table_layout,                  TableLayout)
 COMPUTED_STYLE_PROP(text_align,                    TextAlign)
 COMPUTED_STYLE_PROP(text_combine_upright,          TextCombineUpright)
 COMPUTED_STYLE_PROP(text_decoration,               TextDecoration)
 COMPUTED_STYLE_PROP(text_decoration_color,         TextDecorationColor)
 COMPUTED_STYLE_PROP(text_decoration_line,          TextDecorationLine)
 COMPUTED_STYLE_PROP(text_decoration_style,         TextDecorationStyle)
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -6339,16 +6339,48 @@ nsRuleNode::ComputeDisplayData(void* aSt
 
   // orient: enum, inherit, initial
   SetDiscrete(*aRuleData->ValueForOrient(),
               display->mOrient, conditions,
               SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL,
               parentDisplay->mOrient,
               NS_STYLE_ORIENT_INLINE, 0, 0, 0, 0);
 
+  // shape-outside: none | [ <basic-shape> || <shape-box> ] | <image>
+  const nsCSSValue* shapeOutsideValue = aRuleData->ValueForShapeOutside();
+  switch (shapeOutsideValue->GetUnit()) {
+    case eCSSUnit_Null:
+      break;
+    case eCSSUnit_None:
+    case eCSSUnit_Initial:
+    case eCSSUnit_Unset:
+      display->mShapeOutside = StyleShapeOutside();
+      break;
+    case eCSSUnit_Inherit:
+      conditions.SetUncacheable();
+      display->mShapeOutside = parentDisplay->mShapeOutside;
+      break;
+    case eCSSUnit_URL: {
+      display->mShapeOutside = StyleShapeOutside();
+      nsIURI* url = shapeOutsideValue->GetURLValue();
+      if (url) {
+        display->mShapeOutside.SetURL(url);
+      }
+      break;
+    }
+    case eCSSUnit_Array: {
+      display->mShapeOutside = StyleShapeOutside();
+      SetStyleShapeOutsideToCSSValue(&display->mShapeOutside, shapeOutsideValue,
+                                     aContext, mPresContext, conditions);
+      break;
+    }
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unrecognized shape-outside unit!");
+  }
+
   COMPUTE_END_RESET(Display, display)
 }
 
 const void*
 nsRuleNode::ComputeVisibilityData(void* aStartStruct,
                                   const nsRuleData* aRuleData,
                                   nsStyleContext* aContext,
                                   nsRuleNode* aHighestNode,
@@ -9707,16 +9739,57 @@ nsRuleNode::SetStyleClipPathToCSSValue(n
 
   if (basicShape) {
     aStyleClipPath->SetBasicShape(basicShape, sizingBox);
   } else {
     aStyleClipPath->SetSizingBox(sizingBox);
   }
 }
 
+void
+nsRuleNode::SetStyleShapeOutsideToCSSValue(StyleShapeOutside* aShapeOutside,
+                                           const nsCSSValue* aValue,
+                                           nsStyleContext* aStyleContext,
+                                           nsPresContext* aPresContext,
+                                           RuleNodeCacheConditions& aConditions)
+{
+  MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Array,
+             "Expect a basic shape or shape box!");
+
+  const nsCSSValue::Array* array = aValue->GetArrayValue();
+  MOZ_ASSERT(array->Count() == 1 || array->Count() == 2,
+             "Expect one or both of a shape function and a shape box!");
+
+  StyleShapeBox shapeBox = StyleShapeBox::Margin;
+  RefPtr<nsStyleBasicShape> basicShape;
+
+  for (size_t i = 0; i < array->Count(); ++i) {
+    const nsCSSValue& item = array->Item(i);
+    switch (item.GetUnit()) {
+      case eCSSUnit_Enumerated:
+        shapeBox = static_cast<StyleShapeBox>(item.GetIntValue());
+        break;
+      case eCSSUnit_Function:
+        basicShape = GetStyleBasicShapeFromCSSValue(item, aStyleContext,
+                                                    aPresContext, aConditions);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unrecognized shape-outside unit in the array!");
+        break;
+    }
+  }
+
+  if (basicShape) {
+    aShapeOutside->SetBasicShape(basicShape, shapeBox);
+  } else {
+    aShapeOutside->SetShapeBox(shapeBox);
+  }
+}
+
+
 // Returns true if the nsStyleFilter was successfully set using the nsCSSValue.
 bool
 nsRuleNode::SetStyleFilterToCSSValue(nsStyleFilter* aStyleFilter,
                                      const nsCSSValue& aValue,
                                      nsStyleContext* aStyleContext,
                                      nsPresContext* aPresContext,
                                      RuleNodeCacheConditions& aConditions)
 {
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -798,16 +798,21 @@ protected:
                                 nsStyleContext* aStyleContext,
                                 nsPresContext* aPresContext,
                                 mozilla::RuleNodeCacheConditions& aConditions);
   void SetStyleClipPathToCSSValue(nsStyleClipPath* aStyleClipPath,
                                   const nsCSSValue* aValue,
                                   nsStyleContext* aStyleContext,
                                   nsPresContext* aPresContext,
                                   mozilla::RuleNodeCacheConditions& aConditions);
+  void SetStyleShapeOutsideToCSSValue(mozilla::StyleShapeOutside* aShapeOutside,
+                                      const nsCSSValue* aValue,
+                                      nsStyleContext* aStyleContext,
+                                      nsPresContext* aPresContext,
+                                      mozilla::RuleNodeCacheConditions& aConditions);
 
 private:
   nsRuleNode(nsPresContext* aPresContext, nsRuleNode* aParent,
              nsIStyleRule* aRule, mozilla::SheetType aLevel, bool aIsImportant);
   ~nsRuleNode();
 
 public:
   // This is infallible; it will never return nullptr.
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2792,16 +2792,142 @@ mozilla::StyleAnimation::operator==(cons
          mName == aOther.mName &&
          mDirection == aOther.mDirection &&
          mFillMode == aOther.mFillMode &&
          mPlayState == aOther.mPlayState &&
          mIterationCount == aOther.mIterationCount;
 }
 
 // --------------------
+// StyleShapeOutside
+//
+StyleShapeOutside::StyleShapeOutside()
+{
+  Reset();
+}
+
+StyleShapeOutside::StyleShapeOutside(const StyleShapeOutside& aSource)
+{
+  switch (aSource.mType) {
+    case Type::None:
+      Reset();
+      break;
+    case Type::ShapeBox:
+      SetShapeBox(aSource.mShapeBox);
+      break;
+    case Type::BasicShape:
+      SetBasicShape(aSource.mBasicShape, aSource.mShapeBox);
+      break;
+    case Type::URL:
+      SetURL(aSource.mURL);
+      break;
+  }
+}
+
+StyleShapeOutside::~StyleShapeOutside()
+{
+  Reset();
+}
+
+StyleShapeOutside&
+StyleShapeOutside::operator=(const StyleShapeOutside& aOther)
+{
+  if (this == &aOther) {
+    return *this;
+  }
+
+  switch (aOther.mType) {
+    case Type::None:
+      Reset();
+      break;
+    case Type::ShapeBox:
+      SetShapeBox(aOther.mShapeBox);
+      break;
+    case Type::BasicShape:
+      SetBasicShape(aOther.mBasicShape, aOther.mShapeBox);
+      break;
+    case Type::URL:
+      SetURL(aOther.mURL);
+      break;
+  }
+
+  return *this;
+}
+
+bool
+StyleShapeOutside::operator==(const StyleShapeOutside& aOther) const
+{
+  if (mType != aOther.mType) {
+    return false;
+  }
+
+  switch (mType) {
+    case Type::None:
+      return true;
+    case Type::ShapeBox:
+      return mShapeBox == aOther.mShapeBox;
+    case Type::BasicShape:
+      return *mBasicShape == *aOther.mBasicShape &&
+        mShapeBox == aOther.mShapeBox;
+    case Type::URL:
+      return EqualURIs(mURL, aOther.mURL);
+  }
+}
+
+void
+StyleShapeOutside::Reset()
+{
+  switch (mType) {
+    case Type::None:
+    case Type::ShapeBox:
+      break;
+    case Type::BasicShape:
+      NS_IF_RELEASE(mBasicShape);
+      break;
+    case Type::URL:
+      NS_IF_RELEASE(mURL);
+      break;
+  }
+
+  mType = Type::None;
+  mShapeBox = StyleShapeBox::None;
+  mBasicShape = nullptr;
+}
+
+void
+StyleShapeOutside::SetShapeBox(StyleShapeBox aShapeBox)
+{
+  Reset();
+  mShapeBox = aShapeBox;
+  mType = Type::ShapeBox;
+}
+
+void
+StyleShapeOutside::SetBasicShape(nsStyleBasicShape* aBasicShape,
+                                 StyleShapeBox aShapeBox)
+{
+  MOZ_ASSERT(aBasicShape, "Expect a valid aBasicShape pointer!");
+  Reset();
+  mBasicShape = aBasicShape;
+  mBasicShape->AddRef();
+  mShapeBox = aShapeBox;
+  mType = Type::BasicShape;
+}
+
+void
+StyleShapeOutside::SetURL(nsIURI* aURL)
+{
+  MOZ_ASSERT(aURL, "Expect a valid aURL pointer!");
+  Reset();
+  mURL = aURL;
+  mURL->AddRef();
+  mType = Type::URL;
+}
+
+// --------------------
 // nsStyleDisplay
 //
 nsStyleDisplay::nsStyleDisplay(StyleStructContext aContext)
   : mDisplay(NS_STYLE_DISPLAY_INLINE)
   , mOriginalDisplay(NS_STYLE_DISPLAY_INLINE)
   , mContain(NS_STYLE_CONTAIN_NONE)
   , mAppearance(NS_THEME_NONE)
   , mPosition(NS_STYLE_POSITION_STATIC)
@@ -2905,16 +3031,17 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   , mAnimationTimingFunctionCount(aSource.mAnimationTimingFunctionCount)
   , mAnimationDurationCount(aSource.mAnimationDurationCount)
   , mAnimationDelayCount(aSource.mAnimationDelayCount)
   , mAnimationNameCount(aSource.mAnimationNameCount)
   , mAnimationDirectionCount(aSource.mAnimationDirectionCount)
   , mAnimationFillModeCount(aSource.mAnimationFillModeCount)
   , mAnimationPlayStateCount(aSource.mAnimationPlayStateCount)
   , mAnimationIterationCountCount(aSource.mAnimationIterationCountCount)
+  , mShapeOutside(aSource.mShapeOutside)
 {
   MOZ_COUNT_CTOR(nsStyleDisplay);
 
   /* Copy over transform origin. */
   mTransformOrigin[0] = aSource.mTransformOrigin[0];
   mTransformOrigin[1] = aSource.mTransformOrigin[1];
   mTransformOrigin[2] = aSource.mTransformOrigin[2];
   mPerspectiveOrigin[0] = aSource.mPerspectiveOrigin[0];
@@ -2984,17 +3111,18 @@ nsChangeHint nsStyleDisplay::CalcDiffere
   // XXX the following is conservative, for now: changing float breaking shouldn't
   // necessarily require a repaint, reflow should suffice.
   if (mBreakType != aOther.mBreakType
       || mBreakInside != aOther.mBreakInside
       || mBreakBefore != aOther.mBreakBefore
       || mBreakAfter != aOther.mBreakAfter
       || mAppearance != aOther.mAppearance
       || mOrient != aOther.mOrient
-      || mOverflowClipBox != aOther.mOverflowClipBox)
+      || mOverflowClipBox != aOther.mOverflowClipBox
+      || mShapeOutside != aOther.mShapeOutside)
     NS_UpdateHint(hint, NS_CombineHint(nsChangeHint_AllReflowHints,
                                        nsChangeHint_RepaintFrame));
 
   if (mIsolation != aOther.mIsolation) {
     NS_UpdateHint(hint, nsChangeHint_RepaintFrame);
   }
 
   /* If we've added or removed the transform property, we need to reconstruct the frame to add
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -33,16 +33,17 @@
 #include "imgRequestProxy.h"
 #include "Orientation.h"
 #include "CounterStyleManager.h"
 #include <cstddef> // offsetof()
 #include <utility>
 
 class nsIFrame;
 class nsIURI;
+class nsStyleBasicShape;
 class nsStyleContext;
 class nsTextFrame;
 class imgIContainer;
 struct nsStyleVisibility;
 
 // Includes nsStyleStructID.
 #include "nsStyleStructFwd.h"
 
@@ -2453,16 +2454,66 @@ private:
   float mDelay;
   nsString mName; // empty string for 'none'
   dom::PlaybackDirection mDirection;
   dom::FillMode mFillMode;
   uint8_t mPlayState;
   float mIterationCount; // mozilla::PositiveInfinity<float>() means infinite
 };
 
+// shape-box for shape-outside
+enum class StyleShapeBox : uint8_t {
+  None,
+  Margin,
+  Border,
+  Padding,
+  Content
+};
+
+struct StyleShapeOutside
+{
+  enum class Type : uint8_t {
+    None,
+    ShapeBox,
+    BasicShape,
+    URL
+  };
+
+  StyleShapeOutside();
+  StyleShapeOutside(const StyleShapeOutside& aSource);
+  ~StyleShapeOutside();
+  StyleShapeOutside& operator=(const StyleShapeOutside& aOther);
+
+  bool operator==(const StyleShapeOutside& aOther) const;
+  bool operator!=(const StyleShapeOutside& aOther) const {
+    return !(*this == aOther);
+  }
+
+  void Reset();
+
+  Type GetType() const { return mType; }
+
+  StyleShapeBox GetShapeBox() const { return mShapeBox; }
+  void SetShapeBox(StyleShapeBox aShapeBox);
+
+  nsStyleBasicShape* GetBasicShape() const { return mBasicShape; }
+  void SetBasicShape(nsStyleBasicShape* aBasicShape,
+                     StyleShapeBox aShapeBox = StyleShapeBox::Margin);
+  nsIURI* GetURL() const { return mURL; }
+  void SetURL(nsIURI* aURL);
+
+private:
+  Type mType = Type::None;
+  StyleShapeBox mShapeBox = StyleShapeBox::None;
+  union {
+    nsStyleBasicShape* mBasicShape;
+    nsIURI* mURL;
+  };
+};
+
 } // namespace mozilla
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
 {
   explicit nsStyleDisplay(StyleStructContext aContext);
   nsStyleDisplay(const nsStyleDisplay& aOther);
   ~nsStyleDisplay() {
     MOZ_COUNT_DTOR(nsStyleDisplay);
@@ -2573,16 +2624,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
            mAnimationDurationCount,
            mAnimationDelayCount,
            mAnimationNameCount,
            mAnimationDirectionCount,
            mAnimationFillModeCount,
            mAnimationPlayStateCount,
            mAnimationIterationCountCount;
 
+  mozilla::StyleShapeOutside mShapeOutside; // [reset]
+
   bool IsBlockInsideStyle() const {
     return NS_STYLE_DISPLAY_BLOCK == mDisplay ||
            NS_STYLE_DISPLAY_LIST_ITEM == mDisplay ||
            NS_STYLE_DISPLAY_INLINE_BLOCK == mDisplay ||
            NS_STYLE_DISPLAY_TABLE_CAPTION == mDisplay;
     // Should TABLE_CELL be included here?  They have
     // block frames nested inside of them.
     // (But please audit all callers before changing.)
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5518,195 +5518,217 @@ if (IsCSSPropertyPrefEnabled("svg.transf
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "border-box" ],
     other_values: [ "fill-box", "view-box" ],
     invalid_values: []
   };
 }
 
+var basicShapeOtherValues = [
+  "polygon(20px 20px)",
+  "polygon(20px 20%)",
+  "polygon(20% 20%)",
+  "polygon(20rem 20em)",
+  "polygon(20cm 20mm)",
+  "polygon(20px 20px, 30px 30px)",
+  "polygon(20px 20px, 30% 30%, 30px 30px)",
+  "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)",
+  "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)",
+
+  "content-box",
+  "padding-box",
+  "border-box",
+  "margin-box",
+
+  "polygon(0 0) content-box",
+  "border-box polygon(0 0)",
+  "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",
+
+  "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)",
+
+  "inset(1px)",
+  "inset(20% -20px)",
+  "inset(20em 4rem calc(20% + 20px))",
+  "inset(20vh 20vw 20pt 3%)",
+  "inset(5px round 3px)",
+  "inset(1px 2px round 3px / 3px)",
+  "inset(1px 2px 3px round 3px 2em / 20%)",
+  "inset(1px 2px 3px 4px round 3px 2vw 20% / 20px 3em 2vh 20%)",
+];
+
+var basicShapeInvalidValues = [
+  "url(#test) url(#tes2)",
+  "polygon (0 0)",
+  "polygon(20px, 40px)",
+  "border-box content-box",
+  "polygon(0 0) polygon(0 0)",
+  "polygon(nonzero 0 0)",
+  "polygon(evenodd 20px 20px)",
+  "polygon(20px 20px, evenodd)",
+  "polygon(20px 20px, nonzero)",
+  "polygon(0 0) conten-box content-box",
+  "content-box polygon(0 0) conten-box",
+  "padding-box polygon(0 0) conten-box",
+  "polygon(0 0) polygon(0 0) content-box",
+  "polygon(0 0) content-box polygon(0 0)",
+  "polygon(0 0), content-box",
+  "polygon(0 0), polygon(0 0)",
+  "content-box polygon(0 0) polygon(0 0)",
+  "content-box polygon(0 0) none",
+  "none content-box polygon(0 0)",
+  "inherit content-box polygon(0 0)",
+  "initial polygon(0 0)",
+  "polygon(0 0) farthest-side",
+  "farthest-corner polygon(0 0)",
+  "polygon(0 0) farthest-corner",
+  "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)",
+  "circle(calc(20px + rubbish))",
+
+  "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)",
+  "ellipse(at top left calc(20px + rubbish))",
+
+  "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%)",
+
+  "inset()",
+  "inset(round)",
+  "inset(round 3px)",
+  "inset(1px round 1px 2px 3px 4px 5px)",
+  "inset(1px 2px 3px 4px 5px)",
+  "inset(1px, round 3px)",
+  "inset(1px, 2px)",
+  "inset(1px 2px, 3px)",
+  "inset(1px at 3px)",
+  "inset(1px round 1px // 2px)",
+  "inset(1px round)",
+  "inset(1px calc(2px + rubbish))",
+  "inset(1px round 2px calc(3px + rubbish))",
+];
+
+var basicShapeUnbalancedValues = [
+  "polygon(30% 30%",
+  "polygon(nonzero, 20% 20px",
+  "polygon(evenodd, 20px 20px",
+
+  "circle(",
+  "circle(40% at 50% 100%",
+  "ellipse(",
+  "ellipse(40% at 50% 100%",
+
+  "inset(1px",
+  "inset(1px 2px",
+  "inset(1px 2px 3px",
+  "inset(1px 2px 3px 4px",
+  "inset(1px 2px 3px 4px round 5px",
+  "inset(1px 2px 3px 4px round 5px / 6px",
+];
+
 if (IsCSSPropertyPrefEnabled("layout.css.clip-path-shapes.enabled")) {
   gCSSProperties["clip-path"] = {
     domProp: "clipPath",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "none" ],
     other_values: [
       // SVG reference clip-path
       "url(#my-clip-path)",
 
-      "polygon(20px 20px)",
-      "polygon(20px 20%)",
-      "polygon(20% 20%)",
-      "polygon(20rem 20em)",
-      "polygon(20cm 20mm)",
-      "polygon(20px 20px, 30px 30px)",
-      "polygon(20px 20px, 30% 30%, 30px 30px)",
-      "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)",
-      "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)",
-
-      "content-box",
-      "padding-box",
-      "border-box",
-      "margin-box",
       "fill-box",
       "stroke-box",
       "view-box",
 
-      "polygon(0 0) content-box",
-      "border-box polygon(0 0)",
-      "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)",
-
-      "inset(1px)",
-      "inset(20% -20px)",
-      "inset(20em 4rem calc(20% + 20px))",
-      "inset(20vh 20vw 20pt 3%)",
-      "inset(5px round 3px)",
-      "inset(1px 2px round 3px / 3px)",
-      "inset(1px 2px 3px round 3px 2em / 20%)",
-      "inset(1px 2px 3px 4px round 3px 2vw 20% / 20px 3em 2vh 20%)",
-    ],
-    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)",
-      "polygon(evenodd 20px 20px)",
-      "polygon(20px 20px, evenodd)",
-      "polygon(20px 20px, nonzero)",
-      "polygon(0 0) conten-box content-box",
-      "content-box polygon(0 0) conten-box",
-      "padding-box polygon(0 0) conten-box",
-      "polygon(0 0) polygon(0 0) content-box",
-      "polygon(0 0) content-box polygon(0 0)",
-      "polygon(0 0), content-box",
-      "polygon(0 0), polygon(0 0)",
-      "content-box polygon(0 0) polygon(0 0)",
-      "content-box polygon(0 0) none",
-      "none content-box polygon(0 0)",
-      "inherit content-box polygon(0 0)",
-      "initial polygon(0 0)",
-      "polygon(0 0) farthest-side",
-      "farthest-corner polygon(0 0)",
-      "polygon(0 0) farthest-corner",
-      "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)",
-      "circle(calc(20px + rubbish))",
-
-      "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)",
-      "ellipse(at top left calc(20px + rubbish))",
-
-      "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%)",
-
-      "inset()",
-      "inset(round)",
-      "inset(round 3px)",
-      "inset(1px round 1px 2px 3px 4px 5px)",
-      "inset(1px 2px 3px 4px 5px)",
-      "inset(1px, round 3px)",
-      "inset(1px, 2px)",
-      "inset(1px 2px, 3px)",
-      "inset(1px at 3px)",
-      "inset(1px round 1px // 2px)",
-      "inset(1px round)",
-      "inset(1px calc(2px + rubbish))",
-      "inset(1px round 2px calc(3px + rubbish))",
-    ],
-    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%",
-
-      "inset(1px",
-      "inset(1px 2px",
-      "inset(1px 2px 3px",
-      "inset(1px 2px 3px 4px",
-      "inset(1px 2px 3px 4px round 5px",
-      "inset(1px 2px 3px 4px round 5px / 6px",
-    ]
+    ].concat(basicShapeOtherValues),
+    invalid_values: basicShapeInvalidValues,
+    unbalanced_values: basicShapeUnbalancedValues,
+  };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.shape-outside.enabled")) {
+  gCSSProperties["shape-outside"] = {
+    domProp: "shapeOutside",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "none" ],
+    other_values: [
+      "url(#my-shape-outside)",
+    ].concat(basicShapeOtherValues),
+    invalid_values: basicShapeInvalidValues,
+    unbalanced_values: basicShapeUnbalancedValues,
   };
 }
 
 
 if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {
   gCSSProperties["filter"] = {
     domProp: "filter",
     inherited: false,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2513,16 +2513,23 @@ pref("layout.css.scroll-behavior.spring-
 // reduced speed without overshooting.
 // When equal to 1.0, the system is critically-damped; it will reach the target
 // at the greatest speed without overshooting.
 pref("layout.css.scroll-behavior.damping-ratio", "1.0");
 
 // Is support for scroll-snap enabled?
 pref("layout.css.scroll-snap.enabled", true);
 
+// Is support for CSS shape-outside enabled?
+#ifdef NIGHTLY_BUILD
+pref("layout.css.shape-outside.enabled", true);
+#else
+pref("layout.css.shape-outside.enabled", false);
+#endif
+
 // Is support for document.fonts enabled?
 pref("layout.css.font-loading-api.enabled", true);
 
 // Should stray control characters be rendered visibly?
 #ifdef RELEASE_BUILD
 pref("layout.css.control-characters.visible", false);
 #else
 pref("layout.css.control-characters.visible", true);