Consolidated shape-margin patch. draft
authorBrad Werth <bwerth@mozilla.com>
Thu, 08 Feb 2018 11:00:56 -0800
changeset 785239 0dc5c3230e709d5c0d698a224c40200e6c3e8b64
parent 784946 8ed49dd81059dfdd876cf62ad5def1cfa56ffbbf
child 785240 4f5ae2324799e6b14b8824b3fbfcfaf11e899b7c
push id107180
push userbwerth@mozilla.com
push dateThu, 19 Apr 2018 20:53:59 +0000
milestone61.0a1
Consolidated shape-margin patch. MozReview-Commit-ID: 99pLZsFA9mj
layout/generic/nsFloatManager.cpp
layout/style/nsCSSPropList.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
servo/components/style/properties/longhand/box.mako.rs
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-002.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-003.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-004.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-006.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-007.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-008.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-009.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-010.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-011.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-018.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-019.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-020.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-021.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-022.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-023.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-024.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-017.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-018.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-019.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-020.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-021.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-022.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-024.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-025.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-026.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-028.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-029.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-018.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-019.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-020.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-021.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-022.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-023.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-024.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-025.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html.ini
testing/web-platform/tests/css/css-shapes/shape-outside/shape-image/shape-image-010.html
testing/web-platform/tests/css/css-shapes/shape-outside/shape-image/shape-image-024.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html
testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -548,48 +548,57 @@ public:
                                       const nsSize& aContainerSize)
   {
     return nsRect(aRect.LineLeft(aWM, aContainerSize), aRect.BStart(aWM),
                   aRect.ISize(aWM), aRect.BSize(aWM));
   }
 
   static UniquePtr<ShapeInfo> CreateShapeBox(
     nsIFrame* const aFrame,
+    nscoord aShapeMargin,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateBasicShape(
     const UniquePtr<StyleBasicShape>& aBasicShape,
+    nscoord aShapeMargin,
+    nsIFrame* const aFrame,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateInset(
     const UniquePtr<StyleBasicShape>& aBasicShape,
+    nscoord aShapeMargin,
+    nsIFrame* aFrame,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateCircleOrEllipse(
     const UniquePtr<StyleBasicShape>& aBasicShape,
+    nscoord aShapeMargin,
+    nsIFrame* const aFrame,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreatePolygon(
     const UniquePtr<StyleBasicShape>& aBasicShape,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateImageShape(
     const UniquePtr<nsStyleImage>& aShapeImage,
     float aShapeImageThreshold,
+    nscoord aShapeMargin,
     nsIFrame* const aFrame,
+    const LogicalRect& aMarginRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
 protected:
   // Compute the minimum line-axis difference between the bounding shape
   // box and its rounded corner within the given band (block-axis region).
   // This is used as a helper function to compute the LineRight() and
   // LineLeft(). See the picture in the implementation for an example.
@@ -612,147 +621,542 @@ protected:
                                        WritingMode aWM,
                                        const nsSize& aContainerSize);
 
   // Convert the half corner radii (nscoord[8]) to the special logical
   // coordinate space used in float manager.
   static UniquePtr<nscoord[]> ConvertToFloatLogical(
     const nscoord aRadii[8],
     WritingMode aWM);
+
+  // Some ShapeInfo subclasses may define their float areas in intervals.
+  // Each interval is a rectangle that is one device pixel deep in the block
+  // axis. The values are stored as block edges in the y coordinates,
+  // and inline edges as the x coordinates. Interval arrays should be sorted
+  // on increasing y values. This function uses a binary search to find the
+  // first interval that contains aTargetY. If no such interval exists, this
+  // function returns aIntervals.Length().
+  static size_t MinIntervalIndexContainingY(const nsTArray<nsRect>& aIntervals,
+                                            const nscoord aTargetY);
 };
 
 /////////////////////////////////////////////////////////////////////////////
+// EllipseShapeInfo
+//
+// Implements shape-outside: circle() and shape-outside: ellipse().
+//
+class nsFloatManager::EllipseShapeInfo final : public nsFloatManager::ShapeInfo
+{
+public:
+  // Construct the float area using math to calculate the shape boundary.
+  // This is the fast path and should be used when shape-margin is negligible,
+  // or when the two values of aRadii are roughly equal. Those two conditions
+  // are defined by ShapeMarginIsNegligible() and RadiiAreRoughlyEqual(). In
+  // those cases, we can conveniently represent the entire float area using
+  // an ellipse.
+  EllipseShapeInfo(const nsPoint& aCenter,
+                   const nsSize& aRadii,
+                   nscoord aShapeMargin);
+
+  // Construct the float area using rasterization to calculate the shape
+  // boundary. This constructor accounts for the fact that applying
+  // 'shape-margin' to an ellipse produces a shape that is not mathematically
+  // representable as an ellipse.
+  EllipseShapeInfo(const nsPoint& aCenter,
+                   const nsSize& aRadii,
+                   nscoord aShapeMargin,
+                   int32_t aAppUnitsPerDevPixel);
+
+  static bool ShapeMarginIsNegligible(nscoord aShapeMargin) {
+    // For now, only return true for a shape-margin of 0. In the future, if
+    // we want to enable use of the fast-path constructor more often, this
+    // limit could be increased;
+    static const nscoord SHAPE_MARGIN_NEGLIGIBLE_MAX(0);
+    return aShapeMargin <= SHAPE_MARGIN_NEGLIGIBLE_MAX;
+  }
+
+  static bool RadiiAreRoughlyEqual(const nsSize& aRadii) {
+    // For now, only return true when we are exactly equal. In the future, if
+    // we want to enable use of the fast-path constructor more often, this
+    // could be generalized to allow radii that are in some close proportion
+    // to each other.
+    return aRadii.width == aRadii.height;
+  }
+  nscoord LineEdge(const nscoord aBStart,
+                   const nscoord aBEnd,
+                   bool aLeft) const;
+  nscoord LineLeft(const nscoord aBStart,
+                   const nscoord aBEnd) const override;
+  nscoord LineRight(const nscoord aBStart,
+                    const nscoord aBEnd) const override;
+  nscoord BStart() const override {
+    return mCenter.y - mRadii.height - mShapeMargin;
+  }
+  nscoord BEnd() const override {
+    return mCenter.y + mRadii.height + mShapeMargin;
+  }
+  bool IsEmpty() const override {
+    return mRadii.IsEmpty() && mShapeMargin == 0;
+  }
+
+  void Translate(nscoord aLineLeft, nscoord aBlockStart) override
+  {
+    for (nsRect& interval : mIntervals) {
+      interval.MoveBy(aLineLeft, aBlockStart);
+    }
+
+    mCenter.MoveBy(aLineLeft, aBlockStart);
+  }
+
+private:
+  // The position of the center of the ellipse. The coordinate space is the
+  // same as FloatInfo::mRect.
+  nsPoint mCenter;
+  // The radii of the ellipse in app units. The width and height represent
+  // the line-axis and block-axis radii of the ellipse.
+  nsSize mRadii;
+  // The shape-margin of the ellipse in app units. If this value is greater
+  // than zero, then we calculate the bounds of the ellipse + margin using
+  // numerical methods and store the values in mIntervals.
+  nscoord mShapeMargin;
+
+  // An interval is slice of the float area defined by this EllipseShapeInfo.
+  // Each interval is a rectangle that is one pixel deep in the block
+  // axis. The values are stored as block edges in the y coordinates,
+  // and inline edges as the x coordinates.
+
+  // The intervals are stored in ascending order on y.
+  nsTArray<nsRect> mIntervals;
+};
+
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
+                                                   const nsSize& aRadii,
+                                                   nscoord aShapeMargin)
+  : mCenter(aCenter)
+  , mRadii(aRadii)
+  , mShapeMargin(0) // We intentionally ignore the value of aShapeMargin here.
+{
+  MOZ_ASSERT(RadiiAreRoughlyEqual(aRadii) ||
+             ShapeMarginIsNegligible(aShapeMargin),
+             "This constructor should only be called when margin is "
+             "negligible or radii are roughly equal.");
+
+  // We add aShapeMargin into the radii, and we earlier stored a mShapeMargin
+  // of zero.
+  mRadii.width += aShapeMargin;
+  mRadii.height += aShapeMargin;
+}
+
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
+                                                   const nsSize& aRadii,
+                                                   nscoord aShapeMargin,
+                                                   int32_t aAppUnitsPerDevPixel)
+  : mCenter(aCenter)
+  , mRadii(aRadii)
+  , mShapeMargin(aShapeMargin)
+{
+  MOZ_ASSERT(!(RadiiAreRoughlyEqual(aRadii) ||
+               ShapeMarginIsNegligible(aShapeMargin)),
+             "This constructor should not be called when margin is "
+             "negligible or radii are roughly equal.");
+
+  // We have to calculate a distance field from the ellipse edge, then build
+  // intervals based on pixels with less than aShapeMargin distance to an
+  // edge pixel.
+
+  // mCenter and mRadii have already been translated into logical coordinates.
+  // x = inline, y = block. Due to symmetry, we only need to calculate the
+  // distance field for one quadrant of the ellipse. We choose the positive-x,
+  // positive-y quadrant (the lower right quadrant in horizontal-tb writing
+  // mode). We choose this quadrant because it allows us to traverse our
+  // distance field in memory order, which is more cache efficient.
+  // When we apply these intervals in LineLeft() and LineRight(), we
+  // account for block ranges that hit other quadrants, or hit multiple
+  // quadrants.
+
+  // Given this setup, computing the distance field is a one-pass O(n)
+  // operation that runs from block top-to-bottom, inline left-to-right. We
+  // use a chamfer 5-7-11 5x5 matrix to compute minimum distance to an edge
+  // pixel. This integer math computation is reasonably close to the true
+  // Euclidean distance. The distances will be approximately 5x the true
+  // distance, quantized in integer units. The 5x is factored away in the
+  // comparison which builds the intervals.
+
+  // Our distance field has to be able to hold values equal to the
+  // maximum shape-margin value that we care about faithfully rendering,
+  // times 5. A 16-bit unsigned int can represent up to ~ 65K which means
+  // we can handle a margin up to ~ 13K device pixels. That's good enough
+  // for practical usage. Any supplied shape-margin value higher than this
+  // maximum will be clamped.
+  typedef uint16_t dfType;
+  const dfType MAX_CHAMFER_VALUE = 11;
+  const dfType MAX_MARGIN = (std::numeric_limits<dfType>::max() -
+                             MAX_CHAMFER_VALUE) / 5;
+  const dfType MAX_MARGIN_5X = MAX_MARGIN * 5;
+
+  // Convert aShapeMargin to dev pixels, convert that into 5x-dev-pixel
+  // space, then clamp to MAX_MARGIN_5X.
+  float shapeMarginDevPixels =
+    NSAppUnitsToFloatPixels(aShapeMargin, aAppUnitsPerDevPixel);
+  int32_t shapeMarginDevPixelsInt5X =
+    NSToIntRound(5.0f * shapeMarginDevPixels);
+  NS_WARNING_ASSERTION(shapeMarginDevPixelsInt5X <= MAX_MARGIN_5X,
+                       "shape-margin is too large and is being clamped.");
+  dfType usedMargin5X = (dfType)std::min((int32_t)MAX_MARGIN_5X,
+                                         shapeMarginDevPixelsInt5X);
+
+  nsSize radiiPlusShapeMargin(mRadii.width + aShapeMargin,
+                              mRadii.height + aShapeMargin);
+  const LayoutDeviceIntSize bounds =
+    LayoutDevicePixel::FromAppUnitsRounded(radiiPlusShapeMargin,
+                                           aAppUnitsPerDevPixel);
+  // Since our distance field is computed with a 5x5 neighborhood, but only
+  // looks in the negative block and negative inline directions, it is
+  // effectively a 3x3 neighborhood. We need to expand our distance field by
+  // a further 2 pixels in both axes, on the leading block edge and the
+  // leading inline edge. We call this edge area the expanded region.
+  static const int32_t iExpand = 2;
+  static const int32_t bExpand = 2;
+  const int32_t iSize = bounds.width + iExpand;
+  const int32_t bSize = bounds.height + bExpand;
+  auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
+  if (!df) {
+    // Without a distance field, we can't reason about the float area.
+    return;
+  }
+
+  // First pass setting distance field, in negative block direction, three
+  // cases:
+  // 1) Expanded region pixel: set to MAX_MARGIN_5X.
+  // 2) Pixel within the ellipse: set to 0.
+  // 3) Other pixel: set to minimum neighborhood distance value, computed
+  //                 with 5-7-11 chamfer.
+
+  for (int32_t b = 0; b < bSize; ++b) {
+    bool bIsInExpandedRegion(b < bExpand);
+    nscoord bInAppUnits = (b - bExpand) * aAppUnitsPerDevPixel;
+    bool bIsMoreThanEllipseBEnd(bInAppUnits > mRadii.height);
+
+    // Find the i intercept of the ellipse edge for this block row, and
+    // adjust it to compensate for the expansion of the inline dimension.
+    // If we're in the expanded region, or if we're using a b that's more
+    // than the bEnd of the ellipse, the intercept is -1 before adjustment.
+    const int32_t iIntercept = iExpand + (
+      (bIsInExpandedRegion || bIsMoreThanEllipseBEnd) ? -1 :
+        NSAppUnitsToIntPixels(
+          XInterceptAtY(bInAppUnits, mRadii.width, mRadii.height),
+          aAppUnitsPerDevPixel));
+
+    // Set iMax in preparation for this block row.
+    int32_t iMax = iIntercept;
+
+    for (int32_t i = 0; i < iSize; ++i) {
+      const int32_t index = i + b * iSize;
+
+      // Handle our three cases, in order.
+      if (i < iExpand ||
+          bIsInExpandedRegion) {
+        // Case 1: Expanded reqion pixel.
+        df[index] = MAX_MARGIN_5X;
+      } else if (i <= iIntercept) {
+        // Case 2: Pixel within the ellipse.
+        df[index] = 0;
+      } else {
+        // Case 3: Other pixel.
+
+        // Backward-looking neighborhood distance from target pixel X
+        // with chamfer 5-7-11 looks like:
+        //
+        // +--+--+--+
+        // |  |11|  |
+        // +--+--+--+
+        // |11| 7| 5|
+        // +--+--+--+
+        // |  | 5| X|
+        // +--+--+--+
+        //
+        // X should be set to the minimum of the values of all of the numbered
+        // neighbors summed with the value in that chamfer cell.
+        df[index] = std::min<dfType>(df[index - 1] + 5,
+                    std::min<dfType>(df[index - iSize] + 5,
+                    std::min<dfType>(df[index - iSize - 1] + 7,
+                    std::min<dfType>(df[index - iSize - 2] + 11,
+                                     df[index - (iSize * 2) - 1] + 11))));
+
+        // Check the df value and see if it's less than or equal to the
+        // usedMargin5X value.
+        if (df[index] <= usedMargin5X) {
+          MOZ_ASSERT(iMax < i);
+          iMax = i;
+        }
+      }
+    }
+
+    if (!bIsInExpandedRegion) {
+      MOZ_ASSERT(!(bIsMoreThanEllipseBEnd && iMax == iIntercept),
+                 "In the shape-margin region, we should always find a pixel "
+                 "within the margin for each block row.");
+      // Origin for this interval is at the center of the ellipse, adjusted
+      // in the positive block direction by bInAppUnits.
+      nsPoint origin(aCenter.x, aCenter.y + bInAppUnits);
+      // Size is an inline iMax plus 1 (to account for the whole pixel) dev
+      // pixels, by 1 block dev pixel. We convert this to app units.
+      nsSize size((iMax - iExpand + 1) * aAppUnitsPerDevPixel,
+                  aAppUnitsPerDevPixel);
+      mIntervals.AppendElement(nsRect(origin, size));
+    }
+  }
+}
+
+nscoord
+nsFloatManager::EllipseShapeInfo::LineEdge(const nscoord aBStart,
+                                           const nscoord aBEnd,
+                                           bool aIsLineLeft) const
+{
+  // If no mShapeMargin, just compute the edge using math.
+  if (mShapeMargin == 0) {
+    nscoord lineDiff =
+      ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
+                                      mRadii.width, mRadii.height,
+                                      mRadii.width, mRadii.height,
+                                      aBStart, aBEnd);
+    return mCenter.x + (aIsLineLeft ? (-mRadii.width + lineDiff) :
+                                      (mRadii.width - lineDiff));
+  }
+
+  // We are checking against our intervals. Make sure we have some.
+  if (mIntervals.IsEmpty()) {
+    NS_WARNING("With mShapeMargin > 0, we can't proceed without intervals.");
+    return 0;
+  }
+
+  // Map aBStart and aBEnd into our intervals. Our intervals are calculated
+  // for the lower-right quadrant (in terms of horizontal-tb writing mode).
+  // If aBStart and aBEnd span the center of the ellipse, then we know we
+  // are at the maximum displacement from the center.
+  bool bStartIsAboveCenter = (aBStart < mCenter.y);
+  bool bEndIsBelowOrAtCenter = (aBEnd >= mCenter.y);
+  if (bStartIsAboveCenter && bEndIsBelowOrAtCenter) {
+    return mCenter.x + (aIsLineLeft ? (-mRadii.width - mShapeMargin) :
+                                      (mRadii.width + mShapeMargin));
+  }
+
+  // aBStart and aBEnd don't span the center. Since the intervals are
+  // strictly wider approaching the center (the start of the mIntervals
+  // array), we only need to find the interval at the block value closest to
+  // the center. We find the min of aBStart, aBEnd, and their reflections --
+  // whichever two of them are within the lower-right quadrant. When we
+  // reflect from the upper-right quadrant to the lower-right, we have to
+  // subtract 1 from the reflection, to account that block values are always
+  // addressed from the leading block edge.
+  nscoord bSmallestWithinIntervals = std::min(
+    bStartIsAboveCenter ? aBStart + (mCenter.y - aBStart) * 2 - 1 : aBStart,
+    bEndIsBelowOrAtCenter ? aBEnd : aBEnd + (mCenter.y - aBEnd) * 2 - 1);
+
+  MOZ_ASSERT(bSmallestWithinIntervals >= mCenter.y &&
+             bSmallestWithinIntervals < BEnd(),
+             "We should have a block value within the intervals.");
+
+  size_t index = MinIntervalIndexContainingY(mIntervals,
+                                             bSmallestWithinIntervals);
+  MOZ_ASSERT(index < mIntervals.Length(),
+             "We should have found a matching interval for this block value.");
+
+  // The interval is storing the line right value. If aIsLineLeft is true,
+  // return the line right value reflected about the center. Again, we
+  // subtract 1 from the reflection to account for leading inline edge.
+  nscoord iLineRight = mIntervals[index].XMost();
+  return aIsLineLeft ? iLineRight - (iLineRight - mCenter.x) * 2 - 1
+                     : iLineRight;
+}
+
+nscoord
+nsFloatManager::EllipseShapeInfo::LineLeft(const nscoord aBStart,
+                                           const nscoord aBEnd) const
+{
+  return LineEdge(aBStart, aBEnd, true);
+}
+
+nscoord
+nsFloatManager::EllipseShapeInfo::LineRight(const nscoord aBStart,
+                                            const nscoord aBEnd) const
+{
+  return LineEdge(aBStart, aBEnd, false);
+}
+
+/////////////////////////////////////////////////////////////////////////////
 // RoundedBoxShapeInfo
 //
 // Implements shape-outside: <shape-box> and shape-outside: inset().
 //
 class nsFloatManager::RoundedBoxShapeInfo final : public nsFloatManager::ShapeInfo
 {
 public:
   RoundedBoxShapeInfo(const nsRect& aRect,
                       UniquePtr<nscoord[]> aRadii)
     : mRect(aRect)
     , mRadii(Move(aRadii))
+    , mShapeMargin(0)
   {}
 
+  RoundedBoxShapeInfo(const nsRect& aRect,
+                      UniquePtr<nscoord[]> aRadii,
+                      nscoord aShapeMargin,
+                      int32_t aAppUnitsPerDevPixel);
+
   nscoord LineLeft(const nscoord aBStart,
                    const nscoord aBEnd) const override;
   nscoord LineRight(const nscoord aBStart,
                     const nscoord aBEnd) const override;
   nscoord BStart() const override { return mRect.y; }
   nscoord BEnd() const override { return mRect.YMost(); }
-  bool IsEmpty() const override { return mRect.IsEmpty(); };
+  bool IsEmpty() const override { return mRect.IsEmpty(); }
 
   void Translate(nscoord aLineLeft, nscoord aBlockStart) override
   {
     mRect.MoveBy(aLineLeft, aBlockStart);
+
+    if (mShapeMargin > 0) {
+      MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalTopRightCorner &&
+                 mLogicalBottomLeftCorner && mLogicalBottomRightCorner,
+                 "If we have positive shape-margin, we should have corners.");
+      mLogicalTopLeftCorner->Translate(aLineLeft, aBlockStart);
+      mLogicalTopRightCorner->Translate(aLineLeft, aBlockStart);
+      mLogicalBottomLeftCorner->Translate(aLineLeft, aBlockStart);
+      mLogicalBottomRightCorner->Translate(aLineLeft, aBlockStart);
+    }
   }
 
 private:
   // The rect of the rounded box shape in the float manager's coordinate
   // space.
   nsRect mRect;
   // The half corner radii of the reference box. It's an nscoord[8] array
   // in the float manager's coordinate space. If there are no radii, it's
   // nullptr.
   UniquePtr<nscoord[]> mRadii;
+
+  // A shape-margin value extends the boundaries of the float area.
+  nscoord mShapeMargin;
+
+  // If mShapeMargin > 0 and any of our mRadii pairs are unequal, we will
+  // construct EllipseShapeInfo objects for each corner.
+  UniquePtr<EllipseShapeInfo> mLogicalTopLeftCorner;
+  UniquePtr<EllipseShapeInfo> mLogicalTopRightCorner;
+  UniquePtr<EllipseShapeInfo> mLogicalBottomLeftCorner;
+  UniquePtr<EllipseShapeInfo> mLogicalBottomRightCorner;
 };
 
+nsFloatManager::RoundedBoxShapeInfo::RoundedBoxShapeInfo(const nsRect& aRect,
+  UniquePtr<nscoord[]> aRadii,
+  nscoord aShapeMargin,
+  int32_t aAppUnitsPerDevPixel)
+  : mRect(aRect)
+  , mRadii(Move(aRadii))
+  , mShapeMargin(aShapeMargin)
+{
+  MOZ_ASSERT(aShapeMargin > 0, "Slow constructor only for shape-margin > 0.");
+
+  // Before we inflate mRect by aShapeMargin, construct each of our corners.
+  // If we do it in this order, it's a bit simpler to calculate the center
+  // of each of the corners.
+  mLogicalTopLeftCorner = MakeUnique<EllipseShapeInfo>(
+    nsPoint(mRect.X() + mRadii[eCornerTopLeftX],
+            mRect.Y() + mRadii[eCornerTopLeftY]),
+    nsSize(mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY]),
+    mShapeMargin, aAppUnitsPerDevPixel);
+
+  mLogicalTopRightCorner = MakeUnique<EllipseShapeInfo>(
+      nsPoint(mRect.XMost() - mRadii[eCornerTopRightX],
+              mRect.Y() + mRadii[eCornerTopRightY]),
+      nsSize(mRadii[eCornerTopRightX], mRadii[eCornerTopRightY]),
+      mShapeMargin, aAppUnitsPerDevPixel);
+
+  mLogicalBottomLeftCorner = MakeUnique<EllipseShapeInfo>(
+      nsPoint(mRect.X() + mRadii[eCornerBottomLeftX],
+              mRect.YMost() - mRadii[eCornerBottomLeftY]),
+      nsSize(mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY]),
+      mShapeMargin, aAppUnitsPerDevPixel);
+
+  mLogicalBottomRightCorner = MakeUnique<EllipseShapeInfo>(
+      nsPoint(mRect.XMost() - mRadii[eCornerBottomRightX],
+              mRect.YMost() - mRadii[eCornerBottomRightY]),
+      nsSize(mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY]),
+      mShapeMargin, aAppUnitsPerDevPixel);
+
+  // Now we inflate our mRect by aShapeMargin.
+  mRect.Inflate(aShapeMargin);
+}
+
 nscoord
 nsFloatManager::RoundedBoxShapeInfo::LineLeft(const nscoord aBStart,
                                               const nscoord aBEnd) const
 {
-  if (!mRadii) {
-    return mRect.x;
+  if (mShapeMargin == 0) {
+    if (!mRadii) {
+      return mRect.x;
+    }
+
+    nscoord lineLeftDiff =
+      ComputeEllipseLineInterceptDiff(
+        mRect.y, mRect.YMost(),
+        mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY],
+        mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY],
+        aBStart, aBEnd);
+    return mRect.x + lineLeftDiff;
   }
 
-  nscoord lineLeftDiff =
-    ComputeEllipseLineInterceptDiff(
-      mRect.y, mRect.YMost(),
-      mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY],
-      mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY],
-      aBStart, aBEnd);
-  return mRect.x + lineLeftDiff;
+  // Determine if aBEnd is within our top corner.
+  if (aBEnd < mLogicalTopLeftCorner->BEnd()) {
+    return mLogicalTopLeftCorner->LineLeft(aBStart, aBEnd);
+  }
+
+  // Determine if aBStart is within our bottom corner.
+  if (aBStart >= mLogicalBottomLeftCorner->BStart()) {
+    return mLogicalBottomLeftCorner->LineLeft(aBStart, aBEnd);
+  }
+
+  // Either aBStart or aBEnd or both are within the flat part of our left
+  // edge. Because we've already inflated our mRect to encompass our
+  // mShapeMargin, we can just return the edge.
+  return mRect.X();
 }
 
 nscoord
 nsFloatManager::RoundedBoxShapeInfo::LineRight(const nscoord aBStart,
                                                const nscoord aBEnd) const
 {
-  if (!mRadii) {
-    return mRect.XMost();
+  if (mShapeMargin == 0) {
+    if (!mRadii) {
+      return mRect.XMost();
+    }
+
+    nscoord lineRightDiff =
+      ComputeEllipseLineInterceptDiff(
+        mRect.y, mRect.YMost(),
+        mRadii[eCornerTopRightX], mRadii[eCornerTopRightY],
+        mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY],
+        aBStart, aBEnd);
+    return mRect.XMost() - lineRightDiff;
   }
 
-  nscoord lineRightDiff =
-    ComputeEllipseLineInterceptDiff(
-      mRect.y, mRect.YMost(),
-      mRadii[eCornerTopRightX], mRadii[eCornerTopRightY],
-      mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY],
-      aBStart, aBEnd);
-  return mRect.XMost() - lineRightDiff;
-}
-
-/////////////////////////////////////////////////////////////////////////////
-// EllipseShapeInfo
-//
-// Implements shape-outside: circle() and shape-outside: ellipse().
-//
-class nsFloatManager::EllipseShapeInfo final : public nsFloatManager::ShapeInfo
-{
-public:
-  EllipseShapeInfo(const nsPoint& aCenter,
-                   const nsSize& aRadii)
-    : mCenter(aCenter)
-    , mRadii(aRadii)
-  {}
-
-  nscoord LineLeft(const nscoord aBStart,
-                   const nscoord aBEnd) const override;
-  nscoord LineRight(const nscoord aBStart,
-                    const nscoord aBEnd) const override;
-  nscoord BStart() const override { return mCenter.y - mRadii.height; }
-  nscoord BEnd() const override { return mCenter.y + mRadii.height; }
-  bool IsEmpty() const override { return mRadii.IsEmpty(); };
-
-  void Translate(nscoord aLineLeft, nscoord aBlockStart) override
-  {
-    mCenter.MoveBy(aLineLeft, aBlockStart);
+  // Determine if aBEnd is within our top corner.
+  if (aBEnd < mLogicalTopRightCorner->BEnd()) {
+    return mLogicalTopRightCorner->LineRight(aBStart, aBEnd);
   }
 
-private:
-  // The position of the center of the ellipse. The coordinate space is the
-  // same as FloatInfo::mRect.
-  nsPoint mCenter;
-  // The radii of the ellipse in app units. The width and height represent
-  // the line-axis and block-axis radii of the ellipse.
-  nsSize mRadii;
-};
+  // Determine if aBStart is within our bottom corner.
+  if (aBStart >= mLogicalBottomRightCorner->BStart()) {
+    return mLogicalBottomRightCorner->LineRight(aBStart, aBEnd);
+  }
 
-nscoord
-nsFloatManager::EllipseShapeInfo::LineLeft(const nscoord aBStart,
-                                           const nscoord aBEnd) const
-{
-  nscoord lineLeftDiff =
-    ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
-                                    mRadii.width, mRadii.height,
-                                    mRadii.width, mRadii.height,
-                                    aBStart, aBEnd);
-  return mCenter.x - mRadii.width + lineLeftDiff;
-}
-
-nscoord
-nsFloatManager::EllipseShapeInfo::LineRight(const nscoord aBStart,
-                                            const nscoord aBEnd) const
-{
-  nscoord lineRightDiff =
-    ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
-                                    mRadii.width, mRadii.height,
-                                    mRadii.width, mRadii.height,
-                                    aBStart, aBEnd);
-  return mCenter.x + mRadii.width - lineRightDiff;
+  // Either aBStart or aBEnd or both are within the flat part of our right
+  // edge. Because we've already inflated our mRect to encompass our
+  // mShapeMargin, we can just return the edge.
+  return mRect.XMost();
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // PolygonShapeInfo
 //
 // Implements shape-outside: polygon().
 //
 class nsFloatManager::PolygonShapeInfo final : public nsFloatManager::ShapeInfo
@@ -969,175 +1373,425 @@ nsFloatManager::PolygonShapeInfo::XInter
 class nsFloatManager::ImageShapeInfo final : public nsFloatManager::ShapeInfo
 {
 public:
   ImageShapeInfo(uint8_t* aAlphaPixels,
                  int32_t aStride,
                  const LayoutDeviceIntSize& aImageSize,
                  int32_t aAppUnitsPerDevPixel,
                  float aShapeImageThreshold,
+                 nscoord aShapeMargin,
                  const nsRect& aContentRect,
+                 const nsRect& aMarginRect,
                  WritingMode aWM,
                  const nsSize& aContainerSize);
 
   nscoord LineLeft(const nscoord aBStart,
                    const nscoord aBEnd) const override;
   nscoord LineRight(const nscoord aBStart,
                     const nscoord aBEnd) const override;
   nscoord BStart() const override { return mBStart; }
   nscoord BEnd() const override { return mBEnd; }
   bool IsEmpty() const override { return mIntervals.IsEmpty(); }
 
   void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
 
 private:
-  size_t MinIntervalIndexContainingY(const nscoord aTargetY) const;
   nscoord LineEdge(const nscoord aBStart,
                    const nscoord aBEnd,
                    bool aLeft) const;
 
   // An interval is slice of the float area defined by this ImageShapeInfo.
   // Each interval is a rectangle that is one pixel deep in the block
   // axis. The values are stored as block edges in the y coordinates,
   // and inline edges as the x coordinates.
 
   // The intervals are stored in ascending order on y.
   nsTArray<nsRect> mIntervals;
 
   nscoord mBStart = nscoord_MAX;
   nscoord mBEnd = nscoord_MIN;
+
+  // CreateInterval transforms the supplied aIMin and aIMax and aB
+  // values into an interval that respects the writing mode. An
+  // aOffsetFromContainer can be provided if the aIMin, aIMax, aB
+  // values were generated relative to something other than the container
+  // rect (such as the content rect or margin rect).
+  void CreateInterval(int32_t aIMin,
+                      int32_t aIMax,
+                      int32_t aB,
+                      int32_t aAppUnitsPerDevPixel,
+                      const nsPoint& aOffsetFromContainer,
+                      WritingMode aWM,
+                      const nsSize& aContainerSize);
 };
 
 nsFloatManager::ImageShapeInfo::ImageShapeInfo(
   uint8_t* aAlphaPixels,
   int32_t aStride,
   const LayoutDeviceIntSize& aImageSize,
   int32_t aAppUnitsPerDevPixel,
   float aShapeImageThreshold,
+  nscoord aShapeMargin,
   const nsRect& aContentRect,
+  const nsRect& aMarginRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   MOZ_ASSERT(aShapeImageThreshold >=0.0 && aShapeImageThreshold <=1.0,
              "The computed value of shape-image-threshold is wrong!");
 
   const uint8_t threshold = NSToIntFloor(aShapeImageThreshold * 255);
   const int32_t w = aImageSize.width;
   const int32_t h = aImageSize.height;
 
-  // Scan the pixels in a double loop. For horizontal writing modes, we do
-  // this row by row, from top to bottom. For vertical writing modes, we do
-  // column by column, from left to right. We define the two loops
-  // generically, then figure out the rows and cols within the i loop.
-  const int32_t bSize = aWM.IsVertical() ? w : h;
-  const int32_t iSize = aWM.IsVertical() ? h : w;
-  for (int32_t b = 0; b < bSize; ++b) {
-    // iMin and iMax store the start and end of the float area for the row
-    // or column represented by this iteration of the b loop.
-    int32_t iMin = -1;
-    int32_t iMax = -1;
+  if (aShapeMargin <= 0) {
+    // Without a positive aShapeMargin, all we have to do is a
+    // direct threshold comparison of the alpha pixels.
+    // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
+
+    // Scan the pixels in a double loop. For horizontal writing modes, we do
+    // this row by row, from top to bottom. For vertical writing modes, we do
+    // column by column, from left to right. We define the two loops
+    // generically, then figure out the rows and cols within the inner loop.
+    const int32_t bSize = aWM.IsVertical() ? w : h;
+    const int32_t iSize = aWM.IsVertical() ? h : w;
+    for (int32_t b = 0; b < bSize; ++b) {
+      // iMin and max store the start and end of the float area for the row
+      // or column represented by this iteration of the outer loop.
+      int32_t iMin = -1;
+      int32_t iMax = -1;
+
+      for (int32_t i = 0; i < iSize; ++i) {
+        const int32_t col = aWM.IsVertical() ? b : i;
+        const int32_t row = aWM.IsVertical() ? i : b;
+        const int32_t index = col + row * aStride;
 
-    for (int32_t i = 0; i < iSize; ++i) {
-      const int32_t col = aWM.IsVertical() ? b : i;
-      const int32_t row = aWM.IsVertical() ? i : b;
+        // Determine if the alpha pixel at this row and column has a value
+        // greater than the threshold. If it does, update our iMin and iMax
+        // values to track the edges of the float area for this row or column.
+        // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
+        const uint8_t alpha = aAlphaPixels[index];
+        if (alpha > threshold) {
+          if (iMin == -1) {
+            iMin = i;
+          }
+          MOZ_ASSERT(iMax < i);
+          iMax = i;
+        }
+      }
 
-      // Determine if the alpha pixel at this row and column has a value
-      // greater than the threshold. If it does, update our iMin and iMax values
-      // to track the edges of the float area for this row or column.
-      // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
-      const uint8_t alpha = aAlphaPixels[col + row * aStride];
-      if (alpha > threshold) {
-        if (iMin == -1) {
-          iMin = i;
-        }
-        MOZ_ASSERT(iMax < i);
-        iMax = i;
+      // At the end of a row or column; did we find something?
+      if (iMin != -1) {
+        // We need to supply an offset of the content rect top left, since
+        // our col and row have been calculated from the content rect,
+        // instead of the margin rect (against which floats are applied).
+        CreateInterval(iMin, iMax, b, aAppUnitsPerDevPixel,
+                       aContentRect.TopLeft(), aWM, aContainerSize);
       }
     }
 
-    // At the end of a row or column; did we find something?
-    if (iMin != -1) {
-      // Store an interval as an nsRect with our inline axis values stored in x
-      // and our block axis values stored in y. The position is dependent on
-      // the writing mode, but the size is the same for all writing modes.
+    if (aWM.IsVerticalRL()) {
+      // vertical-rl or sideways-rl.
+      // Because we scan the columns from left to right, we need to reverse
+      // the array so that it's sorted (in ascending order) on the block
+      // direction.
+      mIntervals.Reverse();
+    }
+  } else {
+    // With a positive aShapeMargin, we have to calculate a distance
+    // field from the opaque pixels, then build intervals based on
+    // them being within aShapeMargin distance to an opaque pixel.
+
+    // Roughly: for each pixel in the margin box, we need to determine the
+    // distance to the nearest opaque image-pixel.  If that distance is less
+    // than aShapeMargin, we consider this margin-box pixel as being part of
+    // the float area.
+
+    // Computing the distance field is a two-pass O(n) operation.
+    // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
+    // to an opaque pixel. This integer math computation is reasonably
+    // close to the true Euclidean distance. The distances will be
+    // approximately 5x the true distance, quantized in integer units.
+    // The 5x is factored away in the comparison used in the final
+    // pass which builds the intervals.
+
+    // Our distance field has to be able to hold values equal to the
+    // maximum shape-margin value that we care about faithfully rendering,
+    // times 5. A 16-bit unsigned int can represent up to ~ 65K which means
+    // we can handle a margin up to ~ 13K device pixels. That's good enough
+    // for practical usage. Any supplied shape-margin value higher than this
+    // maximum will be clamped.
+    typedef uint16_t dfType;
+    const dfType MAX_CHAMFER_VALUE = 11;
+    const dfType MAX_MARGIN = (std::numeric_limits<dfType>::max() -
+                               MAX_CHAMFER_VALUE) / 5;
+    const dfType MAX_MARGIN_5X = MAX_MARGIN * 5;
+
+    // Convert aShapeMargin to dev pixels, convert that into 5x-dev-pixel
+    // space, then clamp to MAX_MARGIN_5X.
+    float shapeMarginDevPixels =
+      NSAppUnitsToFloatPixels(aShapeMargin, aAppUnitsPerDevPixel);
+    int32_t shapeMarginDevPixelsInt5X =
+      NSToIntRound(5.0f * shapeMarginDevPixels);
+    NS_WARNING_ASSERTION(shapeMarginDevPixelsInt5X <= MAX_MARGIN_5X,
+                         "shape-margin is too large and is being clamped.");
+    dfType usedMargin5X = (dfType)std::min((int32_t)MAX_MARGIN_5X,
+                                           shapeMarginDevPixelsInt5X);
 
-      // Size is the difference in inline axis edges stored as x, and one
-      // block axis pixel stored as y. For the inline axis, we add 1 to iMax
-      // because we want to capture the far edge of the last pixel.
-      nsSize size(((iMax + 1) - iMin) * aAppUnitsPerDevPixel,
-                  aAppUnitsPerDevPixel);
+    // Allocate our distance field.  The distance field has to cover
+    // the entire aMarginRect, since aShapeMargin could bleed into it,
+    // beyond the content rect covered by aAlphaPixels. To make this work,
+    // we calculate a dfOffset value which is the top left of the content
+    // rect relative to the margin rect.
+    nsPoint offsetPoint = aContentRect.TopLeft() - aMarginRect.TopLeft();
+    LayoutDeviceIntPoint dfOffset =
+      LayoutDevicePixel::FromAppUnitsRounded(offsetPoint,
+                                             aAppUnitsPerDevPixel);
+
+    // Since our distance field is computed with a 5x5 neighborhood,
+    // we need to expand our distance field by a further 4 pixels in
+    // both axes, 2 on the leading edge and 2 on the trailing edge.
+    // We call this edge area the "expanded region".
+
+    // Since dfOffset will be used in comparisons against expanded region
+    // pixel values, it's convenient to add 2 to dfOffset in both axes, to
+    // simplify comparison math later.
+    dfOffset.x += 2;
+    dfOffset.y += 2;
 
-      // Since we started our scanning of the image pixels from the top left,
-      // the interval position starts from the origin of the content rect,
-      // converted to logical coordinates.
-      nsPoint origin = ConvertToFloatLogical(aContentRect.TopLeft(), aWM,
-                                             aContainerSize);
+    // In all these calculations, we purposely ignore aStride, because
+    // we don't have to replicate the packing that we received in
+    // aAlphaPixels. When we need to convert from df coordinates to
+    // alpha coordinates, we do that with math based on row and col.
+    const LayoutDeviceIntSize marginRectDevPixels =
+      LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
+                                             aAppUnitsPerDevPixel);
+    const int32_t wEx = marginRectDevPixels.width + 4;
+    const int32_t hEx = marginRectDevPixels.height + 4;
+
+    // Since the margin-box size is CSS controlled, and large values will
+    // generate large wEx and hEx values, we do a falliable allocation for
+    // the distance field. If allocation fails, we early exit and layout will
+    // be wrong, but we'll avoid aborting from OOM.
+    auto df = MakeUniqueFallible<dfType[]>(wEx * hEx);
+    if (!df) {
+      // Without a distance field, we can't reason about the float area.
+      return;
+    }
+
+    const int32_t bSize = aWM.IsVertical() ? wEx : hEx;
+    const int32_t iSize = aWM.IsVertical() ? hEx : wEx;
+
+    // First pass setting distance field, starting at top-left, three cases:
+    // 1) Expanded region pixel: set to MAX_MARGIN_5X.
+    // 2) Image pixel with alpha greater than threshold: set to 0.
+    // 3) Other pixel: set to minimum backward-looking neighborhood
+    //                 distance value, computed with 5-7-11 chamfer.
+
+    // Scan the pixels in a double loop. For horizontal writing modes, we do
+    // this row by row, from top to bottom. For vertical writing modes, we do
+    // column by column, from left to right. We define the two loops
+    // generically, then figure out the rows and cols within the inner loop.
+    for (int32_t b = 0; b < bSize; ++b) {
+      for (int32_t i = 0; i < iSize; ++i) {
+        const int32_t col = aWM.IsVertical() ? b : i;
+        const int32_t row = aWM.IsVertical() ? i : b;
+        const int32_t index = col + row * wEx;
 
-      // Depending on the writing mode, we now move the origin.
-      if (aWM.IsVerticalRL()) {
-        // vertical-rl or sideways-rl.
-        // These writing modes proceed from the top right, and each interval
-        // moves in a positive inline direction and negative block direction.
-        // That means that the intervals will be reversed after all have been
-        // constructed. We add 1 to b to capture the end of the block axis pixel.
-        origin.MoveBy(iMin * aAppUnitsPerDevPixel, (b + 1) * -aAppUnitsPerDevPixel);
-      } else if (aWM.IsVerticalLR() && aWM.IsSideways()) {
-        // sideways-lr.
-        // These writing modes proceed from the bottom left, and each interval
-        // moves in a negative inline direction and a positive block direction.
-        // We add 1 to iMax to capture the end of the inline axis pixel.
-        origin.MoveBy((iMax + 1) * -aAppUnitsPerDevPixel, b * aAppUnitsPerDevPixel);
-      } else {
-        // horizontal-tb or vertical-lr.
-        // These writing modes proceed from the top left and each interval
-        // moves in a positive step in both inline and block directions.
-        origin.MoveBy(iMin * aAppUnitsPerDevPixel, b * aAppUnitsPerDevPixel);
+        // Handle our three cases, in order.
+        if (col < 2 ||
+            col >= wEx - 2 ||
+            row < 2 ||
+            row >= hEx - 2) {
+          // Case 1: Expanded pixel.
+          df[index] = MAX_MARGIN_5X;
+        } else if (col >= dfOffset.x &&
+                   col < (dfOffset.x + w) &&
+                   row >= dfOffset.y &&
+                   row < (dfOffset.y + h) &&
+                   aAlphaPixels[col - dfOffset.x +
+                                (row - dfOffset.y) * aStride] > threshold) {
+          // Case 2: Image pixel that is opaque.
+          df[index] = 0;
+        } else {
+          // Case 3: Other pixel.
+
+          // Backward-looking neighborhood distance from target pixel X
+          // with chamfer 5-7-11 looks like:
+          //
+          // +--+--+--+--+--+
+          // |  |11|  |11|  |
+          // +--+--+--+--+--+
+          // |11| 7| 5| 7|11|
+          // +--+--+--+--+--+
+          // |  | 5| X|  |  |
+          // +--+--+--+--+--+
+          //
+          // X should be set to the minimum of MAX_MARGIN_5X and the
+          // values of all of the numbered neighbors summed with the
+          // value in that chamfer cell.
+          df[index] = std::min<dfType>(MAX_MARGIN_5X,
+                      std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
+                      std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
+                      std::min<dfType>(df[index - wEx - 2] + 11,
+                      std::min<dfType>(df[index - wEx - 1] + 7,
+                      std::min<dfType>(df[index - wEx] + 5,
+                      std::min<dfType>(df[index - wEx + 1] + 7,
+                      std::min<dfType>(df[index - wEx + 2] + 11,
+                                       df[index - 1] + 5))))))));
+        }
+      }
+    }
+
+    // Okay, time for the second pass. This pass is in reverse order from
+    // the first pass. All of our opaque pixels have been set to 0, and all
+    // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
+    // pixels have been set to some value between those two (inclusive) but
+    // this hasn't yet taken into account the neighbors that were processed
+    // after them in the first pass. This time we reverse iterate so we can
+    // apply the forward-looking chamfer.
+
+    // This time, we constrain our outer and inner loop to ignore the
+    // expanded region pixels. For each pixel we iterate, we set the df value
+    // to the minimum forward-looking neighborhood distance value, computed
+    // with a 5-7-11 chamfer. We also check each df value against the
+    // usedMargin5X threshold, and use that to set the iMin and iMax values
+    // for the interval we'll create for that block axis value (b).
+
+    // At the end of each row (or column in vertical writing modes),
+    // if any of the other pixels had a value less than usedMargin5X,
+    // we create an interval.
+    for (int32_t b = bSize - 3; b >= 2; --b) {
+      // iMin tracks the first df pixel and iMax the last df pixel whose
+      // df[] value is less than usedMargin5X. Set iMin and iMax in
+      // preparation for this row or column.
+      int32_t iMin = iSize;
+      int32_t iMax = -1;
+
+      for (int32_t i = iSize - 3; i >= 2; --i) {
+        const int32_t col = aWM.IsVertical() ? b : i;
+        const int32_t row = aWM.IsVertical() ? i : b;
+        const int32_t index = col + row * wEx;
+
+        // Only apply the chamfer calculation if the df value is not
+        // already 0, since the chamfer can only reduce the value.
+        if (df[index]) {
+          // Forward-looking neighborhood distance from target pixel X
+          // with chamfer 5-7-11 looks like:
+          //
+          // +--+--+--+--+--+
+          // |  |  | X| 5|  |
+          // +--+--+--+--+--+
+          // |11| 7| 5| 7|11|
+          // +--+--+--+--+--+
+          // |  |11|  |11|  |
+          // +--+--+--+--+--+
+          //
+          // X should be set to the minimum of its current value and
+          // the values of all of the numbered neighbors summed with
+          // the value in that chamfer cell.
+          df[index] = std::min<dfType>(df[index],
+                      std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
+                      std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
+                      std::min<dfType>(df[index + wEx + 2] + 11,
+                      std::min<dfType>(df[index + wEx + 1] + 7,
+                      std::min<dfType>(df[index + wEx] + 5,
+                      std::min<dfType>(df[index + wEx - 1] + 7,
+                      std::min<dfType>(df[index + wEx - 2] + 11,
+                                       df[index + 1] + 5))))))));
+        }
+
+        // Finally, we can check the df value and see if it's less than
+        // or equal to the usedMargin5X value.
+        if (df[index] <= usedMargin5X) {
+          if (iMax == -1) {
+            iMax = i;
+          }
+          MOZ_ASSERT(iMin > i);
+          iMin = i;
+        }
       }
 
-      mIntervals.AppendElement(nsRect(origin, size));
+      if (iMax != -1) {
+        // Our interval values, iMin, iMax, and b are all calculated from
+        // the expanded region, which is based on the margin rect. To create
+        // our interval, we have to subtract 2 from (iMin, iMax, and b) to
+        // account for the expanded region edges.  This produces coords that
+        // are relative to our margin-rect, so we pass in
+        // aMarginRect.TopLeft() to make CreateInterval convert to our
+        // container's coordinate space.
+        CreateInterval(iMin - 2, iMax - 2, b - 2, aAppUnitsPerDevPixel,
+                       aMarginRect.TopLeft(), aWM, aContainerSize);
+      }
     }
-  }
 
-  if (aWM.IsVerticalRL()) {
-    // vertical-rl or sideways-rl.
-    // Because we scan the columns from left to right, we need to reverse
-    // the array so that it's sorted (in ascending order) on the block
-    // direction.
-    mIntervals.Reverse();
+    if (!aWM.IsVerticalRL()) {
+      // Anything other than vertical-rl or sideways-rl.
+      // Because we assembled our intervals on the bottom-up pass,
+      // they are reversed for most writing modes. Reverse them to
+      // keep the array sorted on the block direction.
+      mIntervals.Reverse();
+    }
   }
 
   if (!mIntervals.IsEmpty()) {
     mBStart = mIntervals[0].Y();
     mBEnd = mIntervals.LastElement().YMost();
   }
 }
 
-size_t
-nsFloatManager::ImageShapeInfo::MinIntervalIndexContainingY(
-  const nscoord aTargetY) const
+void
+nsFloatManager::ImageShapeInfo::CreateInterval(
+  int32_t aIMin,
+  int32_t aIMax,
+  int32_t aB,
+  int32_t aAppUnitsPerDevPixel,
+  const nsPoint& aOffsetFromContainer,
+  WritingMode aWM,
+  const nsSize& aContainerSize)
 {
-  // Perform a binary search to find the minimum index of an interval
-  // that contains aTargetY. If no such interval exists, return a value
-  // equal to the number of intervals.
-  size_t startIdx = 0;
-  size_t endIdx = mIntervals.Length();
-  while (startIdx < endIdx) {
-    size_t midIdx = startIdx + (endIdx - startIdx) / 2;
-    if (mIntervals[midIdx].ContainsY(aTargetY)) {
-      return midIdx;
-    }
-    nscoord midY = mIntervals[midIdx].Y();
-    if (midY < aTargetY) {
-      startIdx = midIdx + 1;
-    } else {
-      endIdx = midIdx;
-    }
+  // Store an interval as an nsRect with our inline axis values stored in x
+  // and our block axis values stored in y. The position is dependent on
+  // the writing mode, but the size is the same for all writing modes.
+
+  // Size is the difference in inline axis edges stored as x, and one
+  // block axis pixel stored as y. For the inline axis, we add 1 to aIMax
+  // because we want to capture the far edge of the last pixel.
+  nsSize size(((aIMax + 1) - aIMin) * aAppUnitsPerDevPixel,
+  aAppUnitsPerDevPixel);
+
+  // Since we started our scanning of the image pixels from the top left,
+  // the interval position starts from the origin of the content rect,
+  // converted to logical coordinates.
+  nsPoint origin = ConvertToFloatLogical(aOffsetFromContainer, aWM,
+                                         aContainerSize);
+
+  // Depending on the writing mode, we now move the origin.
+  if (aWM.IsVerticalRL()) {
+    // vertical-rl or sideways-rl.
+    // These writing modes proceed from the top right, and each interval
+    // moves in a positive inline direction and negative block direction.
+    // That means that the intervals will be reversed after all have been
+    // constructed. We add 1 to aB to capture the end of the block axis pixel.
+    origin.MoveBy(aIMin * aAppUnitsPerDevPixel, (aB + 1) * -aAppUnitsPerDevPixel);
+  } else if (aWM.IsVerticalLR() && aWM.IsSideways()) {
+    // sideways-lr.
+    // These writing modes proceed from the bottom left, and each interval
+    // moves in a negative inline direction and a positive block direction.
+    // We add 1 to aIMax to capture the end of the inline axis pixel.
+    origin.MoveBy((aIMax + 1) * -aAppUnitsPerDevPixel, aB * aAppUnitsPerDevPixel);
+  } else {
+    // horizontal-tb or vertical-lr.
+    // These writing modes proceed from the top left and each interval
+    // moves in a positive step in both inline and block directions.
+    origin.MoveBy(aIMin * aAppUnitsPerDevPixel, aB * aAppUnitsPerDevPixel);
   }
 
-  return endIdx;
+  mIntervals.AppendElement(nsRect(origin, size));
 }
 
 nscoord
 nsFloatManager::ImageShapeInfo::LineEdge(const nscoord aBStart,
                                          const nscoord aBEnd,
                                          bool aLeft) const
 {
   MOZ_ASSERT(aBStart <= aBEnd,
@@ -1149,17 +1803,17 @@ nsFloatManager::ImageShapeInfo::LineEdge
 
   // Since the intervals are stored in block-axis order, we need
   // to find the first interval that overlaps aBStart and check
   // succeeding intervals until we get past aBEnd.
 
   nscoord lineEdge = aLeft ? nscoord_MAX : nscoord_MIN;
 
   size_t intervalCount = mIntervals.Length();
-  for (size_t i = MinIntervalIndexContainingY(aBStart);
+  for (size_t i = MinIntervalIndexContainingY(mIntervals, aBStart);
 	   i < intervalCount; ++i) {
     // We can always get the bCoord from the intervals' mLineLeft,
     // since the y() coordinate is duplicated in both points in the
     // interval.
     auto& interval = mIntervals[i];
     nscoord bCoord = interval.Y();
     if (bCoord > aBEnd) {
       break;
@@ -1233,44 +1887,57 @@ nsFloatManager::FloatInfo::FloatInfo(nsI
       return;
 
     case StyleShapeSourceType::URL:
       MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have URL source type!");
       return;
 
     case StyleShapeSourceType::Image: {
       float shapeImageThreshold = mFrame->StyleDisplay()->mShapeImageThreshold;
+      nscoord shapeMargin = nsLayoutUtils::ResolveToLength<true>(
+        mFrame->StyleDisplay()->mShapeMargin,
+        LogicalSize(aWM, aContainerSize).ISize(aWM));
       mShapeInfo = ShapeInfo::CreateImageShape(shapeOutside.GetShapeImage(),
                                                shapeImageThreshold,
+                                               shapeMargin,
                                                mFrame,
+                                               aMarginRect,
                                                aWM,
                                                aContainerSize);
       if (!mShapeInfo) {
         // Image is not ready, or fails to load, etc.
         return;
       }
 
       break;
     }
 
     case StyleShapeSourceType::Box: {
+      nscoord shapeMargin = nsLayoutUtils::ResolveToLength<true>(
+        mFrame->StyleDisplay()->mShapeMargin,
+        LogicalSize(aWM, aContainerSize).ISize(aWM));
       // Initialize <shape-box>'s reference rect.
       LogicalRect shapeBoxRect =
         ShapeInfo::ComputeShapeBoxRect(shapeOutside, mFrame, aMarginRect, aWM);
-      mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeBoxRect, aWM,
+      mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeMargin,
+                                             shapeBoxRect, aWM,
                                              aContainerSize);
       break;
     }
 
     case StyleShapeSourceType::Shape: {
       const UniquePtr<StyleBasicShape>& basicShape = shapeOutside.GetBasicShape();
+      nscoord shapeMargin = nsLayoutUtils::ResolveToLength<true>(
+        mFrame->StyleDisplay()->mShapeMargin,
+        LogicalSize(aWM, aContainerSize).ISize(aWM));
       // Initialize <shape-box>'s reference rect.
       LogicalRect shapeBoxRect =
         ShapeInfo::ComputeShapeBoxRect(shapeOutside, mFrame, aMarginRect, aWM);
-      mShapeInfo = ShapeInfo::CreateBasicShape(basicShape, shapeBoxRect, aWM,
+      mShapeInfo = ShapeInfo::CreateBasicShape(basicShape, shapeMargin, mFrame,
+                                               shapeBoxRect, aWM,
                                                aContainerSize);
       break;
     }
   }
 
   MOZ_ASSERT(mShapeInfo,
              "All shape-outside values except none should have mShapeInfo!");
 
@@ -1409,58 +2076,70 @@ nsFloatManager::ShapeInfo::ComputeShapeB
   }
 
   return rect;
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateShapeBox(
   nsIFrame* const aFrame,
+  nscoord aShapeMargin,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   nsRect logicalShapeBoxRect
     = ConvertToFloatLogical(aShapeBoxRect, aWM, aContainerSize);
 
   nscoord physicalRadii[8];
   bool hasRadii = aFrame->GetShapeBoxBorderRadii(physicalRadii);
-  if (!hasRadii) {
+  if (!hasRadii && aShapeMargin <= 0) {
     return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
                                            UniquePtr<nscoord[]>());
   }
 
+  // Add aShapeMargin to each of the radii.
+  for (int& i : physicalRadii) {
+    physicalRadii[i] += aShapeMargin;
+  }
+
   return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
                                          ConvertToFloatLogical(physicalRadii,
                                                                aWM));
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateBasicShape(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
+  nsIFrame* const aFrame,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   switch (aBasicShape->GetShapeType()) {
     case StyleBasicShapeType::Polygon:
       return CreatePolygon(aBasicShape, aShapeBoxRect, aWM, aContainerSize);
     case StyleBasicShapeType::Circle:
     case StyleBasicShapeType::Ellipse:
-      return CreateCircleOrEllipse(aBasicShape, aShapeBoxRect, aWM,
+      return CreateCircleOrEllipse(aBasicShape, aShapeMargin, aFrame,
+                                   aShapeBoxRect, aWM,
                                    aContainerSize);
     case StyleBasicShapeType::Inset:
-      return CreateInset(aBasicShape, aShapeBoxRect, aWM, aContainerSize);
+      return CreateInset(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect,
+                         aWM, aContainerSize);
   }
   return nullptr;
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateInset(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
+  nsIFrame* aFrame,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   // Use physical coordinates to compute inset() because the top, right,
   // bottom and left offsets are physical.
   // https://drafts.csswg.org/css-shapes-1/#funcdef-inset
   nsRect physicalShapeBoxRect =
@@ -1470,29 +2149,72 @@ nsFloatManager::ShapeInfo::CreateInset(
 
   nsRect logicalInsetRect =
     ConvertToFloatLogical(LogicalRect(aWM, insetRect, aContainerSize),
                           aWM, aContainerSize);
   nscoord physicalRadii[8];
   bool hasRadii =
     ShapeUtils::ComputeInsetRadii(aBasicShape, insetRect, physicalShapeBoxRect,
                                   physicalRadii);
-  if (!hasRadii) {
+
+  // With a zero shape-margin, we will be able to use the fast constructor.
+  if (aShapeMargin == 0) {
+    if (!hasRadii) {
+      return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
+                                             UniquePtr<nscoord[]>());
+    }
     return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
-                                           UniquePtr<nscoord[]>());
+                                           ConvertToFloatLogical(physicalRadii,
+                                                                 aWM));
   }
 
+  // With a positive shape-margin, we might still be able to use the fast
+  // constructor. With no radii, we can build a rounded box by inflating
+  // logicalInsetRect, and supplying aShapeMargin as the radius for all
+  // corners.
+  if (!hasRadii) {
+    logicalInsetRect.Inflate(aShapeMargin);
+    auto logicalRadii = MakeUnique<nscoord[]>(8);
+    for (int32_t i = 0; i < 8; ++i) {
+      logicalRadii[i] = aShapeMargin;
+    }
+    return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
+                                           Move(logicalRadii));
+  }
+
+  // If we have radii, and each pair is equal, we can inflate both
+  // logicalInsetRect and all the radii and use the fast constructor.
+  logicalInsetRect.Inflate(aShapeMargin);
+  if (physicalRadii[0] == physicalRadii[1] &&
+      physicalRadii[2] == physicalRadii[3] &&
+      physicalRadii[4] == physicalRadii[5] &&
+      physicalRadii[6] == physicalRadii[7]) {
+    for (int& i : physicalRadii) {
+      physicalRadii[i] += aShapeMargin;
+    }
+    return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
+                                           ConvertToFloatLogical(physicalRadii,
+                                                                 aWM));
+  }
+
+  // With positive shape-margin and unequal radii pairs, we have to use the
+  // slow constructor.
+  nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+  int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
   return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
                                          ConvertToFloatLogical(physicalRadii,
-                                                               aWM));
+                                                               aWM),
+                                         aShapeMargin, appUnitsPerDevPixel);
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateCircleOrEllipse(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
+  nsIFrame* const aFrame,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   // Use physical coordinates to compute the center of circle() or ellipse()
   // since the <position> keywords such as 'left', 'top', etc. are physical.
   // https://drafts.csswg.org/css-shapes-1/#funcdef-ellipse
   nsRect physicalShapeBoxRect =
@@ -1503,27 +2225,44 @@ nsFloatManager::ShapeInfo::CreateCircleO
     ConvertToFloatLogical(physicalCenter, aWM, aContainerSize);
 
   // Compute the circle or ellipse radii.
   nsSize radii;
   StyleBasicShapeType type = aBasicShape->GetShapeType();
   if (type == StyleBasicShapeType::Circle) {
     nscoord radius = ShapeUtils::ComputeCircleRadius(aBasicShape, physicalCenter,
                                                      physicalShapeBoxRect);
+    // Circles can use the three argument, math constructor for
+    // EllipseShapeInfo.
     radii = nsSize(radius, radius);
-  } else {
-    MOZ_ASSERT(type == StyleBasicShapeType::Ellipse);
-    nsSize physicalRadii =
-      ShapeUtils::ComputeEllipseRadii(aBasicShape, physicalCenter,
-                                      physicalShapeBoxRect);
-    LogicalSize logicalRadii(aWM, physicalRadii);
-    radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
+    return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
   }
 
-  return MakeUnique<EllipseShapeInfo>(logicalCenter, radii);
+  MOZ_ASSERT(type == StyleBasicShapeType::Ellipse);
+  nsSize physicalRadii =
+    ShapeUtils::ComputeEllipseRadii(aBasicShape, physicalCenter,
+                                    physicalShapeBoxRect);
+  LogicalSize logicalRadii(aWM, physicalRadii);
+  radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
+
+  // If radii are close to the same value, or if aShapeMargin is small
+  // enough (as specified in css pixels), then we can use the three argument
+  // constructor for EllipseShapeInfo, which uses math for a more efficient
+  // method of float area computation.
+  if (EllipseShapeInfo::ShapeMarginIsNegligible(aShapeMargin) ||
+      EllipseShapeInfo::RadiiAreRoughlyEqual(radii)) {
+    return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
+  }
+
+  // We have to use the full constructor for EllipseShapeInfo. This
+  // computes the float area using a rasterization method.
+  nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+  int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+  return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin,
+                                      appUnitsPerDevPixel);
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreatePolygon(
   const UniquePtr<StyleBasicShape>& aBasicShape,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
@@ -1545,17 +2284,19 @@ nsFloatManager::ShapeInfo::CreatePolygon
 
   return MakeUnique<PolygonShapeInfo>(Move(vertices));
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateImageShape(
   const UniquePtr<nsStyleImage>& aShapeImage,
   float aShapeImageThreshold,
+  nscoord aShapeMargin,
   nsIFrame* const aFrame,
+  const LogicalRect& aMarginRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   MOZ_ASSERT(aShapeImage ==
              aFrame->StyleDisplay()->mShapeOutside.GetShapeImage(),
              "aFrame should be the frame that we got aShapeImage from");
 
   nsImageRenderer imageRenderer(aFrame, aShapeImage.get(),
@@ -1604,27 +2345,31 @@ nsFloatManager::ShapeInfo::CreateImageSh
 
   if (!map.IsMapped()) {
     return nullptr;
   }
 
   MOZ_ASSERT(sourceSurface->GetSize() == contentSizeInDevPixels.ToUnknownSize(),
              "Who changes the size?");
 
+  nsRect marginRect = aMarginRect.GetPhysicalRect(aWM, aContainerSize);
+
   uint8_t* alphaPixels = map.GetData();
   int32_t stride = map.GetStride();
 
   // NOTE: ImageShapeInfo constructor does not keep a persistent copy of
   // alphaPixels; it's only used during the constructor to compute pixel ranges.
   return MakeUnique<ImageShapeInfo>(alphaPixels,
                                     stride,
                                     contentSizeInDevPixels,
                                     appUnitsPerDevPixel,
                                     aShapeImageThreshold,
+                                    aShapeMargin,
                                     contentRect,
+                                    marginRect,
                                     aWM,
                                     aContainerSize);
 }
 
 /* static */ nscoord
 nsFloatManager::ShapeInfo::ComputeEllipseLineInterceptDiff(
   const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
   const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
@@ -1710,16 +2455,42 @@ nsFloatManager::ShapeInfo::ConvertToFloa
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   LogicalPoint logicalPoint(aWM, aPoint, aContainerSize);
   return nsPoint(logicalPoint.LineRelative(aWM, aContainerSize),
                  logicalPoint.B(aWM));
 }
 
+/* static */ size_t
+nsFloatManager::ShapeInfo::MinIntervalIndexContainingY(
+  const nsTArray<nsRect>& aIntervals,
+  const nscoord aTargetY)
+{
+  // Perform a binary search to find the minimum index of an interval
+  // that contains aTargetY. If no such interval exists, return a value
+  // equal to the number of intervals.
+  size_t startIdx = 0;
+  size_t endIdx = aIntervals.Length();
+  while (startIdx < endIdx) {
+    size_t midIdx = startIdx + (endIdx - startIdx) / 2;
+    if (aIntervals[midIdx].ContainsY(aTargetY)) {
+      return midIdx;
+    }
+    nscoord midY = aIntervals[midIdx].Y();
+    if (midY < aTargetY) {
+      startIdx = midIdx + 1;
+    } else {
+      endIdx = midIdx;
+    }
+  }
+
+  return endIdx;
+}
+
 /* static */ UniquePtr<nscoord[]>
 nsFloatManager::ShapeInfo::ConvertToFloatLogical(const nscoord aRadii[8],
                                                  WritingMode aWM)
 {
   UniquePtr<nscoord[]> logicalRadii(new nscoord[8]);
 
   // Get the physical side for line-left and line-right since border radii
   // are on the physical axis.
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2764,16 +2764,25 @@ CSS_PROP_(
     shape_image_threshold,
     ShapeImageThreshold,
     0,
     "layout.css.shape-outside.enabled",
     VARIANT_HN,
     nullptr,
     eStyleAnimType_float)
 CSS_PROP_(
+    shape-margin,
+    shape_margin,
+    ShapeMargin,
+    0,
+    "layout.css.shape-outside.enabled",
+    VARIANT_HLP | VARIANT_CALC,
+    nullptr,
+    eStyleAnimType_Coord)
+CSS_PROP_(
     shape-outside,
     shape_outside,
     ShapeOutside,
     CSS_PROPERTY_VALUE_PARSER_FUNCTION,
     "layout.css.shape-outside.enabled",
     0,
     nullptr,
     eStyleAnimType_Custom)
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -6564,16 +6564,24 @@ already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetShapeImageThreshold()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetNumber(StyleDisplay()->mShapeImageThreshold);
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetShapeMargin()
+{
+  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+  SetValueToCoord(val, StyleDisplay()->mShapeMargin, true);
+  return val.forget();
+}
+
+already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetShapeOutside()
 {
   return GetShapeSource(StyleDisplay()->mShapeOutside,
                         nsCSSProps::kShapeOutsideShapeBoxKTable);
 }
 
 void
 nsComputedDOMStyle::SetCssTextToCoord(nsAString& aCssText,
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -513,16 +513,17 @@ private:
   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> DoGetShapeImageThreshold();
+  already_AddRefed<CSSValue> DoGetShapeMargin();
   already_AddRefed<CSSValue> DoGetShapeOutside();
 
   /* User interface properties */
   already_AddRefed<CSSValue> DoGetCaretColor();
   already_AddRefed<CSSValue> DoGetCursor();
   already_AddRefed<CSSValue> DoGetForceBrokenImageIcon();
   already_AddRefed<CSSValue> DoGetIMEMode();
   already_AddRefed<CSSValue> DoGetUserFocus();
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -230,16 +230,17 @@ COMPUTED_STYLE_PROP(scale,              
 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_image_threshold,         ShapeImageThreshold)
+COMPUTED_STYLE_PROP(shape_margin,                  ShapeMargin)
 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)
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3580,16 +3580,17 @@ nsStyleDisplay::nsStyleDisplay(const nsP
   , mAnimationTimingFunctionCount(1)
   , mAnimationDurationCount(1)
   , mAnimationDelayCount(1)
   , mAnimationNameCount(1)
   , mAnimationDirectionCount(1)
   , mAnimationFillModeCount(1)
   , mAnimationPlayStateCount(1)
   , mAnimationIterationCountCount(1)
+  , mShapeMargin(0, nsStyleCoord::CoordConstructor)
 {
   MOZ_COUNT_CTOR(nsStyleDisplay);
 
   // Initial value for mScrollSnapDestination is "0px 0px"
   mScrollSnapDestination.SetInitialZeroValues();
 
   mTransitions[0].SetInitialValues();
   mAnimations[0].SetInitialValues();
@@ -3653,16 +3654,17 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   , mAnimationDurationCount(aSource.mAnimationDurationCount)
   , mAnimationDelayCount(aSource.mAnimationDelayCount)
   , mAnimationNameCount(aSource.mAnimationNameCount)
   , mAnimationDirectionCount(aSource.mAnimationDirectionCount)
   , mAnimationFillModeCount(aSource.mAnimationFillModeCount)
   , mAnimationPlayStateCount(aSource.mAnimationPlayStateCount)
   , mAnimationIterationCountCount(aSource.mAnimationIterationCountCount)
   , mShapeImageThreshold(aSource.mShapeImageThreshold)
+  , mShapeMargin(aSource.mShapeMargin)
   , mShapeOutside(aSource.mShapeOutside)
 {
   MOZ_COUNT_CTOR(nsStyleDisplay);
 }
 
 
 static
 void ReleaseSharedListOnMainThread(const char* aName,
@@ -3801,35 +3803,36 @@ nsStyleDisplay::CalcDifference(const nsS
    */
 
   if (mFloat != aNewData.mFloat) {
     // Changing which side we're floating on (float:none was handled above).
     hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
   }
 
   if (mShapeOutside != aNewData.mShapeOutside ||
+      mShapeMargin != aNewData.mShapeMargin ||
       mShapeImageThreshold != aNewData.mShapeImageThreshold) {
     if (aNewData.mFloat != StyleFloat::None) {
-      // If we are floating, and our shape-outside or shape-image-threshold
-      // are changed, our descendants are not impacted, but our ancestor and
-      // siblings are.
+      // If we are floating, and our shape-outside, shape-margin, or
+      // shape-image-threshold are changed, our descendants are not impacted,
+      // but our ancestor and siblings are.
       //
       // This is similar to a float-only change, but since the ISize of the
       // float area changes arbitrarily along its block axis, more is required
       // to get the siblings to adjust properly. Hinting overflow change is
       // sufficient to trigger the correct calculation, but may be too
       // heavyweight.
 
       // XXX What is the minimum hint to ensure mShapeInfo is regenerated in
       // the next reflow?
       hint |= nsChangeHint_ReflowHintsForFloatAreaChange |
               nsChangeHint_CSSOverflowChange;
     } else {
-      // shape-outside or shape-image-threshold changed, but we don't need
-      // to reflow because we're not floating.
+      // shape-outside or shape-margin or shape-image-threshold changed,
+      // but we don't need to reflow because we're not floating.
       hint |= nsChangeHint_NeutralChange;
     }
   }
 
   if (mVerticalAlign != aNewData.mVerticalAlign) {
     // XXX Can this just be AllReflowHints + RepaintFrame, and be included in
     // the block below?
     hint |= NS_STYLE_HINT_REFLOW;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2348,16 +2348,19 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   const nsTimingFunction& GetAnimationTimingFunction(uint32_t aIndex) const
   {
     return mAnimations[aIndex % mAnimationTimingFunctionCount].GetTimingFunction();
   }
 
   // The threshold used for extracting a shape from shape-outside: <image>.
   float mShapeImageThreshold = 0.0f; // [reset]
 
+  // The margin around a shape-outside: <image>.
+  nsStyleCoord mShapeMargin;
+
   mozilla::StyleShapeSource mShapeOutside; // [reset]
 
   bool IsBlockInsideStyle() const {
     return mozilla::StyleDisplay::Block == mDisplay ||
            mozilla::StyleDisplay::ListItem == mDisplay ||
            mozilla::StyleDisplay::InlineBlock == mDisplay ||
            mozilla::StyleDisplay::TableCaption == mDisplay ||
            mozilla::StyleDisplay::FlowRoot == mDisplay;
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -642,20 +642,32 @@
     spec="https://drafts.csswg.org/css-will-change/#will-change"
 )}
 
 ${helpers.predefined_type(
     "shape-image-threshold", "Opacity", "0.0",
     products="gecko",
     gecko_pref="layout.css.shape-outside.enabled",
     animation_value_type="ComputedValue",
+    flags="APPLIES_TO_FIRST_LETTER",
     spec="https://drafts.csswg.org/css-shapes/#shape-image-threshold-property",
 )}
 
 ${helpers.predefined_type(
+    "shape-margin",
+    "NonNegativeLengthOrPercentage",
+    "computed::NonNegativeLengthOrPercentage::zero()",
+    products="gecko",
+    gecko_pref="layout.css.shape-outside.enabled",
+    animation_value_type="NonNegativeLengthOrPercentage",
+    flags="APPLIES_TO_FIRST_LETTER",
+    spec="https://drafts.csswg.org/css-shapes/#shape-margin-property",
+)}
+
+${helpers.predefined_type(
     "shape-outside",
     "basic_shape::FloatAreaShape",
     "generics::basic_shape::ShapeSource::None",
     products="gecko",
     boxed=True,
     gecko_pref="layout.css.shape-outside.enabled",
     animation_value_type="ComputedValue",
     flags="APPLIES_TO_FIRST_LETTER",
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-002.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[shape-outside-radial-gradient-002.html]
-  [CSS Test: Left float with radial gradient and percentage shape margin]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-003.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[shape-outside-radial-gradient-003.html]
-  [CSS Test: Left float with radial gradient shape, shape margin, and shape-image-threshold]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-004.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[shape-outside-radial-gradient-004.html]
-  [CSS Test: Left float with radial gradient shape, shape margin, and shape-image-threshold]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-006.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-006.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-007.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-007.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-008.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-008.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-009.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-009.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-010.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-010.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-011.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-011.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-018.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-018.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-019.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-019.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-020.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-020.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-021.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-021.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-022.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-022.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-023.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-023.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-024.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-image-024.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-017.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-017.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-018.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-018.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-019.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-019.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-020.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-020.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-021.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-021.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-022.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-022.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-024.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-024.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-025.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-025.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-026.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-026.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-027.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-028.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-028.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-029.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-circle-029.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-015.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-017.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-018.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-018.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-019.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-019.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-020.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-020.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-021.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-021.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-022.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-022.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-023.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-023.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-024.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-024.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-025.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-ellipse-025.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-inset-010.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-inset-011.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-inset-012.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-inset-013.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-inset-014.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[shape-outside-inset-015.html]
-  expected: FAIL
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/shape-image/shape-image-010.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/shape-image/shape-image-010.html
@@ -35,17 +35,17 @@
           z-index: 2;
         }
         #shape-image {
             float: left;
             width: 100px;
             height: 100px;
             shape-outside: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAALpJREFUeNrs0UEVABAURcHP5pcRSxpR9FHGhhycuQ3emxI9TnxQ7pxttfH6jhoCIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACAsQFQAQEiIAAERAgAgJEQAQEiIAAEZDPuwAAAP//AwCf+AWUylJrCQAAAABJRU5ErkJggg==);
             shape-margin: 5%;
-            shape-image-threshold: 0.25;
+            shape-image-threshold: 0.7;
         }
         .blue {
             width: 2px;
             height: 100px;
             background-color: blue;
             z-index: 3;
         }
         .left-line { left: 65px; }
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/shape-image/shape-image-024.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/shape-image/shape-image-024.html
@@ -14,26 +14,31 @@
         body {
             margin: 0;
         }
         .container {
           left: 10px;
           font: 100px/1 Ahem;
         }
         #test {
-            width: 200px;
+            width: 240px;
             color: rgb(0,100,0);
         }
         #image {
             float: left;
             width: 200px;
             height: 200px;
             shape-outside: url("support/left-half-rectangle.png");
             shape-margin: 10px;
         }
+        .green {
+            background-color: rgb(0,100,0);
+            width: 100px;
+            height: 200px;
+        }
         .blue {
             width: 2px;
             height: 200px;
             background-color: blue;
         }
         .left-line { left: 115px; }
         .right-line { left: 230px; }
 
@@ -51,16 +56,14 @@
     </style>
 </head>
 <body>
     <p>
         The test passes if the green rectangle on the right is completely between the two blue lines.
         There should be no red.
     </p>
     <div id="test" class="container">
-        <img id="image" src="support/left-half-rectangle.png"/>
-        X<br/>X
-    </div>
+        <div id="image"><div class="green"></div></div>X<br/>X</div>
     <div class="blue left-line"></div>
     <div class="blue right-line"></div>
     <div class="failure"></div>
 </body>
 </html>
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html
@@ -31,17 +31,17 @@
     #test-shape {
         float: right;
         width: 110px;
         height: 110px;
         margin: 10px;
         padding: 10px;
         border: 10px solid transparent;
         shape-margin: 15px;
-        shape-outside: margin-box circle(35% at 85px 75px);
+        shape-outside: margin-box circle(60px);
     }
     #line {
         position: absolute;
         top: 0px;
         left: 140px;
         width: 2px;
         height: 200px;
         border-left: 2px solid blue;
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html
@@ -29,17 +29,17 @@
     }
     #test-shape {
         float: left;
         width: 140px;
         height: 140px;
         margin: 10px;
         padding: 10px;
         border: 10px solid transparent;
-        shape-outside: padding-box ellipse(closest-side at 75px 80px);
+        shape-outside: padding-box ellipse(closest-side closest-side at 75px 80px);
     }
     #line {
         position: absolute;
         top: 0px;
         left: 168px;
         width: 2px;
         height: 200px;
         border-left: 2px solid blue;
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html
@@ -31,17 +31,17 @@
     #test-shape {
         float: left;
         width: 130px;
         height: 130px;
         margin: 10px;
         padding: 10px;
         border: 10px solid transparent;
         shape-margin: 10px;
-        shape-outside: content-box ellipse(farthest-side);
+        shape-outside: content-box ellipse(farthest-side closest-side);
     }
     #line {
         position: absolute;
         top: 0px;
         left: 168px;
         width: 2px;
         height: 200px;
         border-left: 2px solid blue;
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html
@@ -16,16 +16,17 @@
             position: relative;
             margin-left: 25px;
         }
         #test-container {
             width: 200px;
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
         #test-shape {
             float: left;
             width: 200px;
             height: 200px;
             shape-outside: inset(50px 100px 50px 0px);
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html
@@ -16,16 +16,17 @@
             position: relative;
             margin-left: 25px;
         }
         #test-container {
             width: 200px;
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
         #test-shape {
             float: left;
             width: 200px;
             height: 200px;
             shape-outside: inset(25% 50% 25% 0%);
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html
@@ -17,16 +17,17 @@
             position: relative;
             margin-left: 25px;
         }
         #test-container {
             width: 200px;
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
         #test-shape {
             float: left;
             width: 50px;
             height: 50px;
             border: 25px solid red;
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html
@@ -17,16 +17,17 @@
             position: relative;
             margin-left: 25px;
         }
         #test-container {
             width: 200px;
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
         #test-shape {
             float: left;
             width: 50px;
             height: 50px;
             border: 25px solid red;
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html
@@ -17,16 +17,17 @@
             position: relative;
             margin-left: 25px;
         }
         #test-container {
             width: 200px;
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
         #test-shape {
             float: left;
             width: 50px;
             height: 50px;
             border: 25px solid red;
--- a/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html
+++ b/testing/web-platform/tests/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html
@@ -17,16 +17,17 @@
             position: relative;
             margin-left: 25px;
         }
         #test-container {
             width: 200px;
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
         #test-shape {
             float: left;
             width: 50px;
             height: 50px;
             border: 25px solid red;