Bug 1288626 Part 8 - Add shape-outside support to style system. r=heycam draft
authorTing-Yu Lin <aethanyc@gmail.com>
Fri, 22 Jul 2016 15:40:01 +0800
changeset 398407 250fa7a853e548bd03e6dc5b27484fee88e5efab
parent 398406 ba601b9cd00747780b8fa1e14044c98608ababcf
child 398539 613a4fc2e5c9269a7c051b7b0fc2fde2b05c424d
push id25517
push userbmo:tlin@mozilla.com
push dateTue, 09 Aug 2016 03:27:51 +0000
reviewersheycam
bugs1288626
milestone51.0a1
Bug 1288626 Part 8 - Add shape-outside support to style system. r=heycam I have to move the definition of StyleBasicShape and StyleShapeSource prior to where nsStyleDisplay::mShapeOutside is defined since the template struct need to be fully defined before using as a member variable. Use SetIdent() in CreatePrimitiveValueForBasicShapeOrURL() in nsComputedDOMStyle.cpp per bug 1288626 comment 6. MozReview-Commit-ID: 1KZS299CFul
layout/inspector/inDOMUtils.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSValue.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleConsts.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
modules/libpref/init/all.js
testing/profiles/prefs_general.js
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -818,16 +818,17 @@ PropertySupportsVariant(nsCSSProperty aP
 
       case eCSSProperty_border_spacing:
         supported = VARIANT_LENGTH;
         break;
 
       case eCSSProperty_content:
       case eCSSProperty_cursor:
       case eCSSProperty_clip_path:
+      case eCSSProperty_shape_outside:
         supported = VARIANT_URL;
         break;
 
       case eCSSProperty_fill:
       case eCSSProperty_stroke:
         supported = VARIANT_COLOR | VARIANT_URL;
         break;
 
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1061,16 +1061,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 aIsXPoint);
   bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
   enum ParseAnimationOrTransitionShorthandResult {
     eParseAnimationOrTransitionShorthand_Values,
@@ -1319,16 +1320,18 @@ protected:
   void SetParsingCompoundProperty(bool aBool) {
     mParsingCompoundProperty = aBool;
   }
   bool IsParsingCompoundProperty(void) const {
     return mParsingCompoundProperty;
   }
 
   /* Functions for basic shapes */
+  bool ParseReferenceBoxAndBasicShape(nsCSSValue& aValue,
+                                      const KTableEntry aBoxKeywordTable[]);
   bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
   bool ParsePolygonFunction(nsCSSValue& aValue);
   bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
   bool ParseInsetFunction(nsCSSValue& aValue);
   // We parse position values differently for basic-shape, by expanding defaults
   // and replacing keywords with percentages
   bool ParsePositionValueForBasicShape(nsCSSValue& aOut);
 
@@ -11754,16 +11757,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);
@@ -16160,68 +16165,93 @@ CSSParserImpl::ParseBasicShape(nsCSSValu
     return ParseCircleOrEllipseFunction(keyword, aValue);
   case eCSSKeyword_inset:
     return ParseInsetFunction(aValue);
   default:
     return false;
   }
 }
 
+bool
+CSSParserImpl::ParseReferenceBoxAndBasicShape(
+  nsCSSValue& aValue,
+  const KTableEntry aBoxKeywordTable[])
+{
+  nsCSSValue referenceBox;
+  bool hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
+
+  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 reference box nor a basic shape.
+  if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
+    return false;
+  }
+
+  // Check if the second argument is a reference box if the first wasn't.
+  if (!hasBox) {
+    hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
+  }
+
+  RefPtr<nsCSSValue::Array> fullValue =
+    nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1);
+
+  if (hasBox && hasShape) {
+    fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox;
+    fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
+  } else if (hasBox) {
+    fullValue->Item(0) = referenceBox;
+  } 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;
+}
+
 /* Parse a clip-path url to a <clipPath> element or a basic shape. */
 bool CSSParserImpl::ParseClipPath()
 {
   nsCSSValue value;
   if (!ParseSingleTokenVariant(value, VARIANT_HUO, nullptr)) {
     if (!nsLayoutUtils::CSSClipPathShapesEnabled()) {
       // With CSS Clip Path Shapes disabled, we should only accept
       // SVG clipPath reference and none.
       REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
       return false;
     }
 
-    nsCSSValue referenceBox;
-    bool hasBox = ParseEnum(referenceBox, nsCSSProps::kClipPathGeometryBoxKTable);
-
-    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 reference box nor a basic shape.
-    if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
-      return false;
-    }
-
-    // Check if the second argument is a reference box if the first wasn't.
-    if (!hasBox) {
-      hasBox = ParseEnum(referenceBox, nsCSSProps::kClipPathGeometryBoxKTable);
-    }
-
-    RefPtr<nsCSSValue::Array> fullValue =
-      nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1);
-
-    if (hasBox && hasShape) {
-      fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox;
-      fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
-    } else if (hasBox) {
-      fullValue->Item(0) = referenceBox;
-    } else {
-      MOZ_ASSERT(hasShape, "should've bailed if we got neither box nor shape");
-      fullValue->Item(0) = basicShape;
-    }
-
-    value.SetArrayValue(fullValue, eCSSUnit_Array);
+    if (!ParseReferenceBoxAndBasicShape(
+          value, nsCSSProps::kClipPathGeometryBoxKTable)) {
+      return false;
+    }
   }
 
   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;
+  }
+
+  return ParseReferenceBoxAndBasicShape(
+    aValue, nsCSSProps::kShapeOutsideShapeBoxKTable);
+}
+
 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
@@ -3703,16 +3703,28 @@ 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 |
+        CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+    "layout.css.shape-outside.enabled",
+    0,
+    nullptr,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_None) // FIXME: Bug 1289049 for adding animation support
 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
@@ -2336,16 +2336,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::kShapeOutsideShapeBoxKTable[] = {
+  { eCSSKeyword_content_box, StyleShapeOutsideShapeBox::Content },
+  { eCSSKeyword_padding_box, StyleShapeOutsideShapeBox::Padding },
+  { eCSSKeyword_border_box, StyleShapeOutsideShapeBox::Border },
+  { eCSSKeyword_margin_box, StyleShapeOutsideShapeBox::Margin },
+  { 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
@@ -737,16 +737,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 kShapeOutsideShapeBoxKTable[];
   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/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -1423,16 +1423,22 @@ nsCSSValue::AppendToString(nsCSSProperty
       break;
 
     case eCSSProperty_clip_path:
       AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
                             nsCSSProps::kClipPathGeometryBoxKTable),
                          aResult);
       break;
 
+    case eCSSProperty_shape_outside:
+      AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+                            nsCSSProps::kShapeOutsideShapeBoxKTable),
+                         aResult);
+      break;
+
     case eCSSProperty_contain:
       if (intValue & NS_STYLE_CONTAIN_STRICT) {
         NS_ASSERTION(intValue == (NS_STYLE_CONTAIN_STRICT | NS_STYLE_CONTAIN_ALL_BITS),
                      "contain: strict should imply contain: layout style paint");
         // Only output strict.
         intValue = NS_STYLE_CONTAIN_STRICT;
       }
       nsStyleUtil::AppendBitmaskCSSValue(aProperty,
@@ -2517,17 +2523,18 @@ nsCSSValuePairList::AppendToString(nsCSS
       item->mYValue.AppendToString(aProperty, aResult, aSerialization);
     }
     item = item->mNext;
     if (!item)
       break;
 
     if (nsCSSProps::PropHasFlags(aProperty,
                                  CSS_PROPERTY_VALUE_LIST_USES_COMMAS) ||
-        aProperty == eCSSProperty_clip_path)
+        aProperty == eCSSProperty_clip_path ||
+        aProperty == eCSSProperty_shape_outside)
       aResult.Append(char16_t(','));
     aResult.Append(char16_t(' '));
   }
 }
 
 /* static */ bool
 nsCSSValuePairList::Equal(const nsCSSValuePairList* aList1,
                           const nsCSSValuePairList* aList2)
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5989,70 +5989,85 @@ nsComputedDOMStyle::CreatePrimitiveValue
       NS_NOTREACHED("unexpected type");
   }
   shapeFunctionString.Append(')');
   RefPtr<nsROCSSPrimitiveValue> functionValue = new nsROCSSPrimitiveValue;
   functionValue->SetString(shapeFunctionString);
   return functionValue.forget();
 }
 
-already_AddRefed<CSSValue>
-nsComputedDOMStyle::CreatePrimitiveValueForClipPath(
+template<typename ReferenceBox>
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::CreatePrimitiveValueForShapeSource(
   const StyleBasicShape* aStyleBasicShape,
-  StyleClipPathGeometryBox aSizingBox)
+  ReferenceBox aReferenceBox,
+  const KTableEntry aBoxKeywordTable[])
 {
   RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
   if (aStyleBasicShape) {
     valueList->AppendCSSValue(
       CreatePrimitiveValueForBasicShape(aStyleBasicShape));
   }
 
-  if (aSizingBox == StyleClipPathGeometryBox::NoBox) {
+  if (aReferenceBox == ReferenceBox::NoBox) {
     return valueList.forget();
   }
 
-  nsAutoString boxString;
-  AppendASCIItoUTF16(
-    nsCSSProps::ValueToKeyword(aSizingBox,
-                               nsCSSProps::kClipPathGeometryBoxKTable),
-                               boxString);
-  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetString(boxString);
+  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+  val->SetIdent(nsCSSProps::ValueToKeywordEnum(aReferenceBox, aBoxKeywordTable));
   valueList->AppendCSSValue(val.forget());
 
   return valueList.forget();
 }
 
-already_AddRefed<CSSValue>
-nsComputedDOMStyle::DoGetClipPath()
-{
-  const nsStyleSVGReset* svg = StyleSVGReset();
-  switch (svg->mClipPath.GetType()) {
+template<typename ReferenceBox>
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetShapeSource(
+  const StyleShapeSource<ReferenceBox>& aShapeSource,
+  const KTableEntry aBoxKeywordTable[])
+{
+  switch (aShapeSource.GetType()) {
     case StyleShapeSourceType::Shape:
-      return CreatePrimitiveValueForClipPath(svg->mClipPath.GetBasicShape(),
-                                             svg->mClipPath.GetReferenceBox());
+      return CreatePrimitiveValueForShapeSource(aShapeSource.GetBasicShape(),
+                                                aShapeSource.GetReferenceBox(),
+                                                aBoxKeywordTable);
     case StyleShapeSourceType::Box:
-      return CreatePrimitiveValueForClipPath(nullptr,
-                                             svg->mClipPath.GetReferenceBox());
+      return CreatePrimitiveValueForShapeSource(nullptr,
+                                                aShapeSource.GetReferenceBox(),
+                                                aBoxKeywordTable);
     case StyleShapeSourceType::URL: {
       RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-      SetValueToFragmentOrURL(svg->mClipPath.GetURL(), val);
+      SetValueToFragmentOrURL(aShapeSource.GetURL(), val);
       return val.forget();
     }
     case StyleShapeSourceType::None_: {
       RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
       val->SetIdent(eCSSKeyword_none);
       return val.forget();
     }
     default:
       NS_NOTREACHED("unexpected type");
   }
   return nullptr;
 }
 
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetClipPath()
+{
+  return GetShapeSource(StyleSVGReset()->mClipPath,
+                        nsCSSProps::kClipPathGeometryBoxKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetShapeOutside()
+{
+  return GetShapeSource(StyleDisplay()->mShapeOutside,
+                        nsCSSProps::kShapeOutsideShapeBoxKTable);
+}
+
 void
 nsComputedDOMStyle::SetCssTextToCoord(nsAString& aCssText,
                                       const nsStyleCoord& aCoord)
 {
   RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
   bool clampNegativeCalc = true;
   SetValueToCoord(value, aCoord, clampNegativeCalc);
   value->GetCssText(aCssText);
--- 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,19 +641,27 @@ private:
   bool GetFrameBorderRectWidth(nscoord& aWidth);
   bool GetFrameBorderRectHeight(nscoord& aHeight);
 
   /* 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(
+  template<typename ReferenceBox>
+  already_AddRefed<CSSValue>
+  GetShapeSource(const mozilla::StyleShapeSource<ReferenceBox>& aShapeSource,
+                 const KTableEntry aBoxKeywordTable[]);
+
+  template<typename ReferenceBox>
+  already_AddRefed<CSSValue>
+  CreatePrimitiveValueForShapeSource(
     const mozilla::StyleBasicShape* aStyleBasicShape,
-    mozilla::StyleClipPathGeometryBox aSizingBox);
+    ReferenceBox aReferenceBox,
+    const KTableEntry aBoxKeywordTable[]);
 
   // Helper function for computing basic shape styles.
   already_AddRefed<CSSValue> CreatePrimitiveValueForBasicShape(
     const mozilla::StyleBasicShape* 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
@@ -212,16 +212,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_align_last,               TextAlignLast)
 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)
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -114,16 +114,24 @@ SetImageRequest(function<void(imgRequest
   if (aPresContext->IsDynamic()) {
     aCallback(req);
   } else {
     RefPtr<imgRequestProxy> staticReq = nsContentUtils::GetStaticRequest(req);
     aCallback(staticReq);
   }
 }
 
+template<typename ReferenceBox>
+static void
+SetStyleShapeSourceToCSSValue(StyleShapeSource<ReferenceBox>* aShapeSource,
+                              const nsCSSValue* aValue,
+                              nsStyleContext* aStyleContext,
+                              nsPresContext* aPresContext,
+                              RuleNodeCacheConditions& aConditions);
+
 /* Helper function to convert a CSS <position> specified value into its
  * computed-style form. */
 static void
   ComputePositionValue(nsStyleContext* aStyleContext,
                        const nsCSSValue& aValue,
                        nsStyleImageLayers::Position& aComputedValue,
                        RuleNodeCacheConditions& aConditions);
 
@@ -6429,16 +6437,45 @@ nsRuleNode::ComputeDisplayData(void* aSt
 
   // orient: enum, inherit, initial
   SetValue(*aRuleData->ValueForOrient(),
            display->mOrient, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
            parentDisplay->mOrient,
            NS_STYLE_ORIENT_INLINE);
 
+  // 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();
+      display->mShapeOutside.SetURL(shapeOutsideValue);
+      break;
+    }
+    case eCSSUnit_Array: {
+      display->mShapeOutside = StyleShapeOutside();
+      SetStyleShapeSourceToCSSValue(&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,
@@ -9725,54 +9762,52 @@ GetStyleBasicShapeFromCSSValue(const nsC
     }
   } else {
     NS_NOTREACHED("unexpected basic shape function");
   }
 
   return basicShape.forget();
 }
 
+template<typename ReferenceBox>
 static void
-SetStyleClipPathToCSSValue(StyleClipPath* aStyleClipPath,
-                           const nsCSSValue* aValue,
-                           nsStyleContext* aStyleContext,
-                           nsPresContext* aPresContext,
-                           RuleNodeCacheConditions& aConditions)
+SetStyleShapeSourceToCSSValue(
+  StyleShapeSource<ReferenceBox>* aShapeSource,
+  const nsCSSValue* aValue,
+  nsStyleContext* aStyleContext,
+  nsPresContext* aPresContext,
+  RuleNodeCacheConditions& aConditions)
 {
   MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Array,
              "expected a basic shape or reference box");
 
   const nsCSSValue::Array* array = aValue->GetArrayValue();
   MOZ_ASSERT(array->Count() == 1 || array->Count() == 2,
-             "Expect one or both of a shape function and geometry-box");
-
-  StyleClipPathGeometryBox sizingBox = StyleClipPathGeometryBox::NoBox;
+             "Expect one or both of a shape function and a reference box");
+
+  ReferenceBox referenceBox = ReferenceBox::NoBox;
   RefPtr<StyleBasicShape> basicShape;
+
   for (size_t i = 0; i < array->Count(); ++i) {
-    if (array->Item(i).GetUnit() == eCSSUnit_Enumerated) {
-      int32_t type = array->Item(i).GetIntValue();
-      if (type > uint8_t(StyleClipPathGeometryBox::View) ||
-          type < uint8_t(StyleClipPathGeometryBox::NoBox)) {
-        NS_NOTREACHED("unexpected reference box");
-        return;
-      }
-      sizingBox = static_cast<StyleClipPathGeometryBox>(type);
-    } else if (array->Item(i).GetUnit() == eCSSUnit_Function) {
-      basicShape = GetStyleBasicShapeFromCSSValue(array->Item(i), aStyleContext,
+    const nsCSSValue& item = array->Item(i);
+    if (item.GetUnit() == eCSSUnit_Enumerated) {
+      referenceBox = static_cast<ReferenceBox>(item.GetIntValue());
+    } else if (item.GetUnit() == eCSSUnit_Function) {
+      basicShape = GetStyleBasicShapeFromCSSValue(item, aStyleContext,
                                                   aPresContext, aConditions);
     } else {
-      NS_NOTREACHED("unexpected value");
+      MOZ_ASSERT_UNREACHABLE("Unexpected unit!");
       return;
     }
   }
 
   if (basicShape) {
-    aStyleClipPath->SetBasicShape(basicShape, sizingBox);
+    aShapeSource->SetBasicShape(basicShape, referenceBox);
   } else {
-    aStyleClipPath->SetReferenceBox(sizingBox);
+    aShapeSource->SetReferenceBox(referenceBox);
   }
 }
 
 // Returns true if the nsStyleFilter was successfully set using the nsCSSValue.
 static bool
 SetStyleFilterToCSSValue(nsStyleFilter* aStyleFilter,
                          const nsCSSValue& aValue,
                          nsStyleContext* aStyleContext,
@@ -9887,18 +9922,18 @@ nsRuleNode::ComputeSVGResetData(void* aS
       break;
     case eCSSUnit_URL: {
       svgReset->mClipPath = StyleClipPath();
       svgReset->mClipPath.SetURL(clipPathValue);
       break;
     }
     case eCSSUnit_Array: {
       svgReset->mClipPath = StyleClipPath();
-      SetStyleClipPathToCSSValue(&svgReset->mClipPath, clipPathValue, aContext,
-                                 mPresContext, conditions);
+      SetStyleShapeSourceToCSSValue(&svgReset->mClipPath, clipPathValue, aContext,
+                                    mPresContext, conditions);
       break;
     }
     default:
       NS_NOTREACHED("unexpected unit");
   }
 
   // stop-opacity:
   SetFactor(*aRuleData->ValueForStopOpacity(),
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -86,16 +86,25 @@ enum class StyleClipPathGeometryBox : ui
 };
 
 // float-edge
 enum class StyleFloatEdge : uint8_t {
   ContentBox,
   MarginBox,
 };
 
+// shape-box for shape-outside
+enum class StyleShapeOutsideShapeBox : uint8_t {
+  NoBox,
+  Content,
+  Padding,
+  Border,
+  Margin
+};
+
 // Shape source type
 // X11 has a #define for None causing conflicts, so we use None_ here
 enum class StyleShapeSourceType : uint8_t {
   None_,
   URL,
   Shape,
   Box,
 };
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3077,16 +3077,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];
@@ -3301,17 +3302,18 @@ nsStyleDisplay::CalcDifference(const nsS
        mAnimationTimingFunctionCount != aNewData.mAnimationTimingFunctionCount ||
        mAnimationDurationCount != aNewData.mAnimationDurationCount ||
        mAnimationDelayCount != aNewData.mAnimationDelayCount ||
        mAnimationNameCount != aNewData.mAnimationNameCount ||
        mAnimationDirectionCount != aNewData.mAnimationDirectionCount ||
        mAnimationFillModeCount != aNewData.mAnimationFillModeCount ||
        mAnimationPlayStateCount != aNewData.mAnimationPlayStateCount ||
        mAnimationIterationCountCount != aNewData.mAnimationIterationCountCount ||
-       mScrollSnapCoordinate != aNewData.mScrollSnapCoordinate)) {
+       mScrollSnapCoordinate != aNewData.mScrollSnapCoordinate ||
+       mShapeOutside != aNewData.mShapeOutside)) {
     hint |= nsChangeHint_NeutralChange;
   }
 
   return hint;
 }
 
 // --------------------
 // nsStyleVisibility
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2518,16 +2518,269 @@ 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
 };
 
+class StyleBasicShape final
+{
+public:
+  explicit StyleBasicShape(StyleBasicShapeType type)
+    : mType(type),
+      mFillRule(NS_STYLE_FILL_RULE_NONZERO)
+  {
+    mPosition.SetInitialPercentValues(0.5f);
+  }
+
+  StyleBasicShapeType GetShapeType() const { return mType; }
+  nsCSSKeyword GetShapeTypeName() const;
+
+  int32_t GetFillRule() const { return mFillRule; }
+  void SetFillRule(int32_t aFillRule)
+  {
+    MOZ_ASSERT(mType == StyleBasicShapeType::Polygon, "expected polygon");
+    mFillRule = aFillRule;
+  }
+
+  typedef nsStyleImageLayers::Position Position;
+  Position& GetPosition() {
+    MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
+               mType == StyleBasicShapeType::Ellipse,
+               "expected circle or ellipse");
+    return mPosition;
+  }
+  const Position& GetPosition() const {
+    MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
+               mType == StyleBasicShapeType::Ellipse,
+               "expected circle or ellipse");
+    return mPosition;
+  }
+
+  bool HasRadius() const {
+    MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
+    nsStyleCoord zero;
+    zero.SetCoordValue(0);
+    NS_FOR_CSS_HALF_CORNERS(corner) {
+      if (mRadius.Get(corner) != zero) {
+        return true;
+      }
+    }
+    return false;
+  }
+  nsStyleCorners& GetRadius() {
+    MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
+    return mRadius;
+  }
+  const nsStyleCorners& GetRadius() const {
+    MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
+    return mRadius;
+  }
+
+  // mCoordinates has coordinates for polygon or radii for
+  // ellipse and circle.
+  nsTArray<nsStyleCoord>& Coordinates()
+  {
+    return mCoordinates;
+  }
+
+  const nsTArray<nsStyleCoord>& Coordinates() const
+  {
+    return mCoordinates;
+  }
+
+  bool operator==(const StyleBasicShape& aOther) const
+  {
+    return mType == aOther.mType &&
+           mFillRule == aOther.mFillRule &&
+           mCoordinates == aOther.mCoordinates &&
+           mPosition == aOther.mPosition &&
+           mRadius == aOther.mRadius;
+  }
+  bool operator!=(const StyleBasicShape& aOther) const {
+    return !(*this == aOther);
+  }
+
+  NS_INLINE_DECL_REFCOUNTING(StyleBasicShape);
+
+private:
+  ~StyleBasicShape() {}
+
+  StyleBasicShapeType mType;
+  int32_t mFillRule;
+
+  // mCoordinates has coordinates for polygon or radii for
+  // ellipse and circle.
+  nsTArray<nsStyleCoord> mCoordinates;
+  Position mPosition;
+  nsStyleCorners mRadius;
+};
+
+template<typename ReferenceBox>
+struct StyleShapeSource
+{
+  StyleShapeSource()
+    : mURL(nullptr)
+  {}
+
+  StyleShapeSource(const StyleShapeSource& aSource)
+    : StyleShapeSource()
+  {
+    if (aSource.mType == StyleShapeSourceType::URL) {
+      CopyURL(aSource);
+    } else if (aSource.mType == StyleShapeSourceType::Shape) {
+      SetBasicShape(aSource.mBasicShape, aSource.mReferenceBox);
+    } else if (aSource.mType == StyleShapeSourceType::Box) {
+      SetReferenceBox(aSource.mReferenceBox);
+    }
+  }
+
+  ~StyleShapeSource()
+  {
+    ReleaseRef();
+  }
+
+  StyleShapeSource& operator=(const StyleShapeSource& aOther)
+  {
+    if (this == &aOther) {
+      return *this;
+    }
+
+    if (aOther.mType == StyleShapeSourceType::URL) {
+      CopyURL(aOther);
+    } else if (aOther.mType == StyleShapeSourceType::Shape) {
+      SetBasicShape(aOther.mBasicShape, aOther.mReferenceBox);
+    } else if (aOther.mType == StyleShapeSourceType::Box) {
+      SetReferenceBox(aOther.mReferenceBox);
+    } else {
+      ReleaseRef();
+      mReferenceBox = ReferenceBox::NoBox;
+      mType = StyleShapeSourceType::None_;
+    }
+    return *this;
+  }
+
+  bool operator==(const StyleShapeSource& aOther) const
+  {
+    if (mType != aOther.mType) {
+      return false;
+    }
+
+    if (mType == StyleShapeSourceType::URL) {
+      return mURL == aOther.mURL;
+    } else if (mType == StyleShapeSourceType::Shape) {
+      return *mBasicShape == *aOther.mBasicShape &&
+             mReferenceBox == aOther.mReferenceBox;
+    } else if (mType == StyleShapeSourceType::Box) {
+      return mReferenceBox == aOther.mReferenceBox;
+    }
+
+    return true;
+  }
+
+  bool operator!=(const StyleShapeSource& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+  StyleShapeSourceType GetType() const
+  {
+    return mType;
+  }
+
+  FragmentOrURL* GetURL() const
+  {
+    MOZ_ASSERT(mType == StyleShapeSourceType::URL, "Wrong shape source type!");
+    return mURL;
+  }
+
+  bool SetURL(const nsCSSValue* aValue)
+  {
+    if (!aValue->GetURLValue()) {
+      return false;
+    }
+
+    ReleaseRef();
+
+    mURL = new FragmentOrURL();
+    mURL->SetValue(aValue);
+    mType = StyleShapeSourceType::URL;
+    return true;
+  }
+
+  StyleBasicShape* GetBasicShape() const
+  {
+    MOZ_ASSERT(mType == StyleShapeSourceType::Shape, "Wrong shape source type!");
+    return mBasicShape;
+  }
+
+  void SetBasicShape(StyleBasicShape* aBasicShape,
+                     ReferenceBox aReferenceBox)
+  {
+    NS_ASSERTION(aBasicShape, "expected pointer");
+    ReleaseRef();
+    mBasicShape = aBasicShape;
+    mBasicShape->AddRef();
+    mReferenceBox = aReferenceBox;
+    mType = StyleShapeSourceType::Shape;
+  }
+
+  ReferenceBox GetReferenceBox() const
+  {
+    MOZ_ASSERT(mType == StyleShapeSourceType::Box ||
+               mType == StyleShapeSourceType::Shape,
+               "Wrong shape source type!");
+    return mReferenceBox;
+  }
+
+  void SetReferenceBox(ReferenceBox aReferenceBox)
+  {
+    ReleaseRef();
+    mReferenceBox = aReferenceBox;
+    mType = StyleShapeSourceType::Box;
+  }
+
+private:
+  void ReleaseRef()
+  {
+    if (mType == StyleShapeSourceType::Shape) {
+      NS_ASSERTION(mBasicShape, "expected pointer");
+      mBasicShape->Release();
+    } else if (mType == StyleShapeSourceType::URL) {
+      NS_ASSERTION(mURL, "expected pointer");
+      delete mURL;
+    }
+    // Both mBasicShape and mURL are pointers in a union. Nulling one of them
+    // nulls both of them.
+    mURL = nullptr;
+  }
+
+  void CopyURL(const StyleShapeSource& aOther)
+  {
+    ReleaseRef();
+
+    mURL = new FragmentOrURL(*aOther.mURL);
+    mType = StyleShapeSourceType::URL;
+  }
+
+  void* operator new(size_t) = delete;
+
+  union {
+    StyleBasicShape* mBasicShape;
+    FragmentOrURL* mURL;
+  };
+  StyleShapeSourceType mType = StyleShapeSourceType::None_;
+  ReferenceBox mReferenceBox = ReferenceBox::NoBox;
+};
+
+using StyleClipPath = StyleShapeSource<StyleClipPathGeometryBox>;
+using StyleShapeOutside = StyleShapeSource<StyleShapeOutsideShapeBox>;
+
 } // namespace mozilla
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
 {
   explicit nsStyleDisplay(StyleStructContext aContext);
   nsStyleDisplay(const nsStyleDisplay& aOther);
   ~nsStyleDisplay() {
     MOZ_COUNT_DTOR(nsStyleDisplay);
@@ -2639,16 +2892,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.)
@@ -3431,272 +3686,16 @@ private:
     STROKE_WIDTH_CONTEXT       = 0x40,  // stroke-width: context-value
     FILL_OPACITY_SOURCE_SHIFT   = 0,
     STROKE_OPACITY_SOURCE_SHIFT = 2,
   };
 
   uint8_t          mContextFlags;     // [inherited]
 };
 
-namespace mozilla {
-
-class StyleBasicShape final
-{
-public:
-  explicit StyleBasicShape(StyleBasicShapeType type)
-    : mType(type),
-      mFillRule(NS_STYLE_FILL_RULE_NONZERO)
-  {
-    mPosition.SetInitialPercentValues(0.5f);
-  }
-
-  StyleBasicShapeType GetShapeType() const { return mType; }
-  nsCSSKeyword GetShapeTypeName() const;
-
-  int32_t GetFillRule() const { return mFillRule; }
-  void SetFillRule(int32_t aFillRule)
-  {
-    MOZ_ASSERT(mType == StyleBasicShapeType::Polygon, "expected polygon");
-    mFillRule = aFillRule;
-  }
-
-  typedef nsStyleImageLayers::Position Position;
-  Position& GetPosition() {
-    MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
-               mType == StyleBasicShapeType::Ellipse,
-               "expected circle or ellipse");
-    return mPosition;
-  }
-  const Position& GetPosition() const {
-    MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
-               mType == StyleBasicShapeType::Ellipse,
-               "expected circle or ellipse");
-    return mPosition;
-  }
-
-  bool HasRadius() const {
-    MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
-    nsStyleCoord zero;
-    zero.SetCoordValue(0);
-    NS_FOR_CSS_HALF_CORNERS(corner) {
-      if (mRadius.Get(corner) != zero) {
-        return true;
-      }
-    }
-    return false;
-  }
-  nsStyleCorners& GetRadius() {
-    MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
-    return mRadius;
-  }
-  const nsStyleCorners& GetRadius() const {
-    MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
-    return mRadius;
-  }
-
-  // mCoordinates has coordinates for polygon or radii for
-  // ellipse and circle.
-  nsTArray<nsStyleCoord>& Coordinates()
-  {
-    return mCoordinates;
-  }
-
-  const nsTArray<nsStyleCoord>& Coordinates() const
-  {
-    return mCoordinates;
-  }
-
-  bool operator==(const StyleBasicShape& aOther) const
-  {
-    return mType == aOther.mType &&
-           mFillRule == aOther.mFillRule &&
-           mCoordinates == aOther.mCoordinates &&
-           mPosition == aOther.mPosition &&
-           mRadius == aOther.mRadius;
-  }
-  bool operator!=(const StyleBasicShape& aOther) const {
-    return !(*this == aOther);
-  }
-
-  NS_INLINE_DECL_REFCOUNTING(StyleBasicShape);
-
-private:
-  ~StyleBasicShape() {}
-
-  StyleBasicShapeType mType;
-  int32_t mFillRule;
-
-  // mCoordinates has coordinates for polygon or radii for
-  // ellipse and circle.
-  nsTArray<nsStyleCoord> mCoordinates;
-  Position mPosition;
-  nsStyleCorners mRadius;
-};
-
-template<typename ReferenceBox>
-struct StyleShapeSource
-{
-  StyleShapeSource()
-    : mURL(nullptr)
-  {}
-
-  StyleShapeSource(const StyleShapeSource& aSource)
-    : StyleShapeSource()
-  {
-    if (aSource.mType == StyleShapeSourceType::URL) {
-      CopyURL(aSource);
-    } else if (aSource.mType == StyleShapeSourceType::Shape) {
-      SetBasicShape(aSource.mBasicShape, aSource.mReferenceBox);
-    } else if (aSource.mType == StyleShapeSourceType::Box) {
-      SetReferenceBox(aSource.mReferenceBox);
-    }
-  }
-
-  ~StyleShapeSource()
-  {
-    ReleaseRef();
-  }
-
-  StyleShapeSource& operator=(const StyleShapeSource& aOther)
-  {
-    if (this == &aOther) {
-      return *this;
-    }
-
-    if (aOther.mType == StyleShapeSourceType::URL) {
-      CopyURL(aOther);
-    } else if (aOther.mType == StyleShapeSourceType::Shape) {
-      SetBasicShape(aOther.mBasicShape, aOther.mReferenceBox);
-    } else if (aOther.mType == StyleShapeSourceType::Box) {
-      SetReferenceBox(aOther.mReferenceBox);
-    } else {
-      ReleaseRef();
-      mReferenceBox = ReferenceBox::NoBox;
-      mType = StyleShapeSourceType::None_;
-    }
-    return *this;
-  }
-
-  bool operator==(const StyleShapeSource& aOther) const
-  {
-    if (mType != aOther.mType) {
-      return false;
-    }
-
-    if (mType == StyleShapeSourceType::URL) {
-      return mURL == aOther.mURL;
-    } else if (mType == StyleShapeSourceType::Shape) {
-      return *mBasicShape == *aOther.mBasicShape &&
-             mReferenceBox == aOther.mReferenceBox;
-    } else if (mType == StyleShapeSourceType::Box) {
-      return mReferenceBox == aOther.mReferenceBox;
-    }
-
-    return true;
-  }
-
-  bool operator!=(const StyleShapeSource& aOther) const
-  {
-    return !(*this == aOther);
-  }
-
-  StyleShapeSourceType GetType() const
-  {
-    return mType;
-  }
-
-  FragmentOrURL* GetURL() const
-  {
-    MOZ_ASSERT(mType == StyleShapeSourceType::URL, "Wrong shape source type!");
-    return mURL;
-  }
-
-  bool SetURL(const nsCSSValue* aValue)
-  {
-    if (!aValue->GetURLValue()) {
-      return false;
-    }
-
-    ReleaseRef();
-
-    mURL = new FragmentOrURL();
-    mURL->SetValue(aValue);
-    mType = StyleShapeSourceType::URL;
-    return true;
-  }
-
-  StyleBasicShape* GetBasicShape() const
-  {
-    MOZ_ASSERT(mType == StyleShapeSourceType::Shape, "Wrong shape source type!");
-    return mBasicShape;
-  }
-
-  void SetBasicShape(StyleBasicShape* aBasicShape,
-                     ReferenceBox aReferenceBox)
-  {
-    NS_ASSERTION(aBasicShape, "expected pointer");
-    ReleaseRef();
-    mBasicShape = aBasicShape;
-    mBasicShape->AddRef();
-    mReferenceBox = aReferenceBox;
-    mType = StyleShapeSourceType::Shape;
-  }
-
-  ReferenceBox GetReferenceBox() const
-  {
-    MOZ_ASSERT(mType == StyleShapeSourceType::Box ||
-               mType == StyleShapeSourceType::Shape,
-               "Wrong shape source type!");
-    return mReferenceBox;
-  }
-
-  void SetReferenceBox(ReferenceBox aReferenceBox)
-  {
-    ReleaseRef();
-    mReferenceBox = aReferenceBox;
-    mType = StyleShapeSourceType::Box;
-  }
-
-private:
-  void ReleaseRef()
-  {
-    if (mType == StyleShapeSourceType::Shape) {
-      NS_ASSERTION(mBasicShape, "expected pointer");
-      mBasicShape->Release();
-    } else if (mType == StyleShapeSourceType::URL) {
-      NS_ASSERTION(mURL, "expected pointer");
-      delete mURL;
-    }
-    // Both mBasicShape and mURL are pointers in a union. Nulling one of them
-    // nulls both of them.
-    mURL = nullptr;
-  }
-
-  void CopyURL(const StyleShapeSource& aOther)
-  {
-    ReleaseRef();
-
-    mURL = new FragmentOrURL(*aOther.mURL);
-    mType = StyleShapeSourceType::URL;
-  }
-
-  void* operator new(size_t) = delete;
-
-  union {
-    StyleBasicShape* mBasicShape;
-    FragmentOrURL* mURL;
-  };
-  StyleShapeSourceType mType = StyleShapeSourceType::None_;
-  ReferenceBox mReferenceBox = ReferenceBox::NoBox;
-};
-
-using StyleClipPath = StyleShapeSource<StyleClipPathGeometryBox>;
-
-} // namespace mozilla
-
 struct nsStyleFilter
 {
   nsStyleFilter();
   nsStyleFilter(const nsStyleFilter& aSource);
   ~nsStyleFilter();
 
   nsStyleFilter& operator=(const nsStyleFilter& aOther);
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5593,195 +5593,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
@@ -2598,16 +2598,19 @@ 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?
+pref("layout.css.shape-outside.enabled", false);
+
 // 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);
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -171,16 +171,19 @@ user_pref("layout.css.initial-letter.ena
 user_pref("layout.css.object-fit-and-position.enabled", true);
 
 // Enable webkit prefixed CSS features for testing
 user_pref("layout.css.prefixes.webkit", true);
 
 // Enable -webkit-{min|max}-device-pixel-ratio media queries for testing
 user_pref("layout.css.prefixes.device-pixel-ratio-webkit", true);
 
+// Enable CSS shape-outside for testing
+user_pref("layout.css.shape-outside.enabled", true);
+
 // Disable spammy layout warnings because they pollute test logs
 user_pref("layout.spammy_warnings.enabled", false);
 
 // Enable Media Source Extensions for testing
 user_pref("media.mediasource.mp4.enabled", true);
 user_pref("media.mediasource.webm.enabled", true);
 
 // Enable mozContacts