Bug 1341156 - Create nsCSSBorderImageRenderer. r=mattwoodrow draft
authorMorris Tseng <mtseng@mozilla.com>
Mon, 06 Mar 2017 10:39:13 +0800
changeset 493798 2ad7b76750ae812faf26aa79604c67750dbcb5bf
parent 493797 862b6fa20fda270c542a30179185b8980e75611b
child 493799 49b5b506841de1901411d050392712d2a7f90c6e
push id47843
push usermtseng@mozilla.com
push dateMon, 06 Mar 2017 02:45:16 +0000
reviewersmattwoodrow
bugs1341156
milestone54.0a1
Bug 1341156 - Create nsCSSBorderImageRenderer. r=mattwoodrow Basiclly refactor DrawBorderImage function for later re-use. MozReview-Commit-ID: 9KIO6jp14M2
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRenderingBorders.cpp
layout/painting/nsCSSRenderingBorders.h
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -425,25 +425,16 @@ struct ColorStop {
   ColorStop(double aPosition, bool aIsMidPoint, const Color& aColor) :
     mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
   double mPosition; // along the gradient line; 0=start, 1=end
   bool mIsMidpoint;
   Color mColor;
 };
 
 /* Local functions */
-static DrawResult DrawBorderImage(nsPresContext* aPresContext,
-                                  nsRenderingContext& aRenderingContext,
-                                  nsIFrame* aForFrame,
-                                  const nsRect& aBorderArea,
-                                  const nsStyleBorder& aStyleBorder,
-                                  const nsRect& aDirtyRect,
-                                  Sides aSkipSides,
-                                  PaintBorderFlags aFlags);
-
 static nscolor MakeBevelColor(mozilla::Side whichSide, uint8_t style,
                               nscolor aBackgroundColor,
                               nscolor aBorderColor);
 
 static InlineBackgroundData* gInlineBGData = nullptr;
 
 // Initialize any static variables used by nsCSSRendering.
 void nsCSSRendering::Init()
@@ -836,19 +827,33 @@ nsCSSRendering::PaintBorderWithStyleBord
     if (theme &&
         theme->ThemeSupportsWidget(aPresContext, aForFrame,
                                    displayData->mAppearance)) {
       return DrawResult::SUCCESS; // Let the theme handle it.
     }
   }
 
   if (aStyleBorder.IsBorderImageLoaded()) {
-    return DrawBorderImage(aPresContext, aRenderingContext, aForFrame,
-                           aBorderArea, aStyleBorder, aDirtyRect,
-                           aSkipSides, aFlags);
+    DrawResult result;
+
+    uint32_t irFlags = 0;
+    if (aFlags & PaintBorderFlags::SYNC_DECODE_IMAGES) {
+      irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
+    }
+
+    Maybe<nsCSSBorderImageRenderer> renderer =
+      nsCSSBorderImageRenderer::CreateBorderImageRenderer(aPresContext, aForFrame, aBorderArea,
+                                                          aStyleBorder, aDirtyRect, aSkipSides,
+                                                          irFlags, &result);
+    if (!renderer) {
+      return result;
+    }
+
+    return renderer->DrawBorderImage(aPresContext, aRenderingContext,
+                                     aForFrame, aDirtyRect);
   }
 
   DrawResult result = DrawResult::SUCCESS;
 
   // If we had a border-image, but it wasn't loaded, then we should return
   // DrawResult::NOT_READY; we'll want to try again if we do a paint with sync
   // decoding enabled.
   if (aStyleBorder.mBorderImageSource.GetType() != eStyleImageType_Null) {
@@ -4096,337 +4101,16 @@ nsCSSRendering::GetBackgroundLayerRect(n
   nsRect borderArea =
     ::BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
   nsBackgroundLayerState state =
       PrepareImageLayer(aPresContext, aForFrame, aFlags, borderArea,
                              aClipRect, aLayer);
   return state.mFillArea;
 }
 
-static DrawResult
-DrawBorderImage(nsPresContext*       aPresContext,
-                nsRenderingContext&  aRenderingContext,
-                nsIFrame*            aForFrame,
-                const nsRect&        aBorderArea,
-                const nsStyleBorder& aStyleBorder,
-                const nsRect&        aDirtyRect,
-                Sides                aSkipSides,
-                PaintBorderFlags     aFlags)
-{
-  NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
-                  "drawing border image that isn't successfully loaded");
-
-  if (aDirtyRect.IsEmpty()) {
-    return DrawResult::SUCCESS;
-  }
-
-  uint32_t irFlags = 0;
-  if (aFlags & PaintBorderFlags::SYNC_DECODE_IMAGES) {
-    irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
-  }
-  nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, irFlags);
-
-  // Ensure we get invalidated for loads and animations of the image.
-  // We need to do this here because this might be the only code that
-  // knows about the association of the style data with the frame.
-  // XXX We shouldn't really... since if anybody is passing in a
-  // different style, they'll potentially have the wrong size for the
-  // border too.
-  aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
-
-  if (!renderer.PrepareImage()) {
-    return renderer.PrepareResult();
-  }
-
-  // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
-  // in case we need it.
-  gfxContextAutoSaveRestore autoSR;
-
-  // Determine the border image area, which by default corresponds to the
-  // border box but can be modified by 'border-image-outset'.
-  // Note that 'border-radius' do not apply to 'border-image' borders per
-  // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
-  nsRect borderImgArea;
-  nsMargin borderWidths(aStyleBorder.GetComputedBorder());
-  nsMargin imageOutset(aStyleBorder.GetImageOutset());
-  if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
-    borderImgArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
-                                                 aSkipSides, &aStyleBorder);
-    if (borderImgArea.IsEqualEdges(aBorderArea)) {
-      // No need for a clip, just skip the sides we don't want.
-      borderWidths.ApplySkipSides(aSkipSides);
-      imageOutset.ApplySkipSides(aSkipSides);
-      borderImgArea.Inflate(imageOutset);
-    } else {
-      // We're drawing borders around the joined continuation boxes so we need
-      // to clip that to the slice that we want for this frame.
-      borderImgArea.Inflate(imageOutset);
-      imageOutset.ApplySkipSides(aSkipSides);
-      nsRect clip = aBorderArea;
-      clip.Inflate(imageOutset);
-      autoSR.EnsureSaved(aRenderingContext.ThebesContext());
-      aRenderingContext.ThebesContext()->
-        Clip(NSRectToSnappedRect(clip,
-                                 aForFrame->PresContext()->AppUnitsPerDevPixel(),
-                                 *aRenderingContext.GetDrawTarget()));
-    }
-  } else {
-    borderImgArea = aBorderArea;
-    borderImgArea.Inflate(imageOutset);
-  }
-
-  // Calculate the image size used to compute slice points.
-  CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize();
-  nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
-                                                          intrinsicSize,
-                                                          borderImgArea.Size());
-  renderer.SetPreferredSize(intrinsicSize, imageSize);
-
-  // Compute the used values of 'border-image-slice' and 'border-image-width';
-  // we do them together because the latter can depend on the former.
-  nsMargin slice;
-  nsMargin border;
-  NS_FOR_CSS_SIDES(s) {
-    nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
-    int32_t imgDimension = SideIsVertical(s)
-                           ? imageSize.width : imageSize.height;
-    nscoord borderDimension = SideIsVertical(s)
-                           ? borderImgArea.width : borderImgArea.height;
-    double value;
-    switch (coord.GetUnit()) {
-      case eStyleUnit_Percent:
-        value = coord.GetPercentValue() * imgDimension;
-        break;
-      case eStyleUnit_Factor:
-        value = nsPresContext::CSSPixelsToAppUnits(
-          NS_lround(coord.GetFactorValue()));
-        break;
-      default:
-        NS_NOTREACHED("unexpected CSS unit for image slice");
-        value = 0;
-        break;
-    }
-    if (value < 0)
-      value = 0;
-    if (value > imgDimension)
-      value = imgDimension;
-    slice.Side(s) = value;
-
-    coord = aStyleBorder.mBorderImageWidth.Get(s);
-    switch (coord.GetUnit()) {
-      case eStyleUnit_Coord: // absolute dimension
-        value = coord.GetCoordValue();
-        break;
-      case eStyleUnit_Percent:
-        value = coord.GetPercentValue() * borderDimension;
-        break;
-      case eStyleUnit_Factor:
-        value = coord.GetFactorValue() * borderWidths.Side(s);
-        break;
-      case eStyleUnit_Auto:  // same as the slice value, in CSS pixels
-        value = slice.Side(s);
-        break;
-      default:
-        NS_NOTREACHED("unexpected CSS unit for border image area division");
-        value = 0;
-        break;
-    }
-    // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
-    // because we expect value to be non-negative.
-    MOZ_ASSERT(value >= 0);
-    border.Side(s) = NSToCoordRoundWithClamp(value);
-    MOZ_ASSERT(border.Side(s) >= 0);
-  }
-
-  // "If two opposite border-image-width offsets are large enough that they
-  // overlap, their used values are proportionately reduced until they no
-  // longer overlap."
-  uint32_t combinedBorderWidth = uint32_t(border.left) +
-                                 uint32_t(border.right);
-  double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width)
-                  ? borderImgArea.width / double(combinedBorderWidth)
-                  : 1.0;
-  uint32_t combinedBorderHeight = uint32_t(border.top) +
-                                  uint32_t(border.bottom);
-  double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height)
-                  ? borderImgArea.height / double(combinedBorderHeight)
-                  : 1.0;
-  double scale = std::min(scaleX, scaleY);
-  if (scale < 1.0) {
-    border.left *= scale;
-    border.right *= scale;
-    border.top *= scale;
-    border.bottom *= scale;
-    NS_ASSERTION(border.left + border.right <= borderImgArea.width &&
-                 border.top + border.bottom <= borderImgArea.height,
-                 "rounding error in width reduction???");
-  }
-
-  // These helper tables recharacterize the 'slice' and 'width' margins
-  // in a more convenient form: they are the x/y/width/height coords
-  // required for various bands of the border, and they have been transformed
-  // to be relative to the innerRect (for 'slice') or the page (for 'border').
-  enum {
-    LEFT, MIDDLE, RIGHT,
-    TOP = LEFT, BOTTOM = RIGHT
-  };
-  const nscoord borderX[3] = {
-    borderImgArea.x + 0,
-    borderImgArea.x + border.left,
-    borderImgArea.x + borderImgArea.width - border.right,
-  };
-  const nscoord borderY[3] = {
-    borderImgArea.y + 0,
-    borderImgArea.y + border.top,
-    borderImgArea.y + borderImgArea.height - border.bottom,
-  };
-  const nscoord borderWidth[3] = {
-    border.left,
-    borderImgArea.width - border.left - border.right,
-    border.right,
-  };
-  const nscoord borderHeight[3] = {
-    border.top,
-    borderImgArea.height - border.top - border.bottom,
-    border.bottom,
-  };
-  const int32_t sliceX[3] = {
-    0,
-    slice.left,
-    imageSize.width - slice.right,
-  };
-  const int32_t sliceY[3] = {
-    0,
-    slice.top,
-    imageSize.height - slice.bottom,
-  };
-  const int32_t sliceWidth[3] = {
-    slice.left,
-    std::max(imageSize.width - slice.left - slice.right, 0),
-    slice.right,
-  };
-  const int32_t sliceHeight[3] = {
-    slice.top,
-    std::max(imageSize.height - slice.top - slice.bottom, 0),
-    slice.bottom,
-  };
-
-  DrawResult result = DrawResult::SUCCESS;
-
-  // intrinsicSize.CanComputeConcreteSize() return false means we can not
-  // read intrinsic size from aStyleBorder.mBorderImageSource.
-  // In this condition, we pass imageSize(a resolved size comes from
-  // default sizing algorithm) to renderer as the viewport size.
-  Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
-    Nothing() : Some(imageSize);
-  bool hasIntrinsicRatio = intrinsicSize.HasRatio();
-  renderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
-
-  for (int i = LEFT; i <= RIGHT; i++) {
-    for (int j = TOP; j <= BOTTOM; j++) {
-      uint8_t fillStyleH, fillStyleV;
-      nsSize unitSize;
-
-      if (i == MIDDLE && j == MIDDLE) {
-        // Discard the middle portion unless set to fill.
-        if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
-            aStyleBorder.mBorderImageFill) {
-          continue;
-        }
-
-        NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL ==
-                     aStyleBorder.mBorderImageFill,
-                     "Unexpected border image fill");
-
-        // css-background:
-        //     The middle image's width is scaled by the same factor as the
-        //     top image unless that factor is zero or infinity, in which
-        //     case the scaling factor of the bottom is substituted, and
-        //     failing that, the width is not scaled. The height of the
-        //     middle image is scaled by the same factor as the left image
-        //     unless that factor is zero or infinity, in which case the
-        //     scaling factor of the right image is substituted, and failing
-        //     that, the height is not scaled.
-        gfxFloat hFactor, vFactor;
-
-        if (0 < border.left && 0 < slice.left)
-          vFactor = gfxFloat(border.left)/slice.left;
-        else if (0 < border.right && 0 < slice.right)
-          vFactor = gfxFloat(border.right)/slice.right;
-        else
-          vFactor = 1;
-
-        if (0 < border.top && 0 < slice.top)
-          hFactor = gfxFloat(border.top)/slice.top;
-        else if (0 < border.bottom && 0 < slice.bottom)
-          hFactor = gfxFloat(border.bottom)/slice.bottom;
-        else
-          hFactor = 1;
-
-        unitSize.width = sliceWidth[i]*hFactor;
-        unitSize.height = sliceHeight[j]*vFactor;
-        fillStyleH = aStyleBorder.mBorderImageRepeatH;
-        fillStyleV = aStyleBorder.mBorderImageRepeatV;
-
-      } else if (i == MIDDLE) { // top, bottom
-        // Sides are always stretched to the thickness of their border,
-        // and stretched proportionately on the other axis.
-        gfxFloat factor;
-        if (0 < borderHeight[j] && 0 < sliceHeight[j])
-          factor = gfxFloat(borderHeight[j])/sliceHeight[j];
-        else
-          factor = 1;
-
-        unitSize.width = sliceWidth[i]*factor;
-        unitSize.height = borderHeight[j];
-        fillStyleH = aStyleBorder.mBorderImageRepeatH;
-        fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
-
-      } else if (j == MIDDLE) { // left, right
-        gfxFloat factor;
-        if (0 < borderWidth[i] && 0 < sliceWidth[i])
-          factor = gfxFloat(borderWidth[i])/sliceWidth[i];
-        else
-          factor = 1;
-
-        unitSize.width = borderWidth[i];
-        unitSize.height = sliceHeight[j]*factor;
-        fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
-        fillStyleV = aStyleBorder.mBorderImageRepeatV;
-
-      } else {
-        // Corners are always stretched to fit the corner.
-        unitSize.width = borderWidth[i];
-        unitSize.height = borderHeight[j];
-        fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
-        fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
-      }
-
-      nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
-      nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
-      if (subArea.IsEmpty())
-        continue;
-
-      nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
-      result &=
-        renderer.DrawBorderImageComponent(aPresContext,
-                                          aRenderingContext, aDirtyRect,
-                                          destArea, CSSIntRect(intSubArea.x,
-                                                               intSubArea.y,
-                                                               intSubArea.width,
-                                                               intSubArea.height),
-                                          fillStyleH, fillStyleV,
-                                          unitSize, j * (RIGHT + 1) + i,
-                                          svgViewportSize, hasIntrinsicRatio);
-    }
-  }
-
-  return result;
-}
-
 // Begin table border-collapsing section
 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
 // At some point, all functions should be unified to include the additional functionality that these provide
 
 static nscoord
 RoundIntToPixel(nscoord aValue,
                 nscoord aTwipsPerPixel,
                 bool    aRoundDown = false)
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -3524,8 +3524,353 @@ nsCSSBorderRenderer::DrawBorders()
       DrawBorderSides(1 << side);
 
       mDrawTarget->PopClip();
 
       PrintAsStringNewline("---------------- (*)");
     }
   }
 }
+
+/* static */Maybe<nsCSSBorderImageRenderer>
+nsCSSBorderImageRenderer::CreateBorderImageRenderer(nsPresContext* aPresContext,
+                                                    nsIFrame* aForFrame,
+                                                    const nsRect& aBorderArea,
+                                                    const nsStyleBorder& aStyleBorder,
+                                                    const nsRect& aDirtyRect,
+                                                    Sides aSkipSides,
+                                                    uint32_t aFlags,
+                                                    DrawResult* aDrawResult)
+{
+  NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
+                  "drawing border image that isn't successfully loaded");
+  MOZ_ASSERT(aDrawResult);
+
+  if (aDirtyRect.IsEmpty()) {
+    *aDrawResult = DrawResult::SUCCESS;
+    return Nothing();
+  }
+
+  nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource, aFlags);
+  if (!imgRenderer.PrepareImage()) {
+    *aDrawResult = imgRenderer.PrepareResult();
+    return Nothing();
+  }
+
+  // Ensure we get invalidated for loads and animations of the image.
+  // We need to do this here because this might be the only code that
+  // knows about the association of the style data with the frame.
+  // XXX We shouldn't really... since if anybody is passing in a
+  // different style, they'll potentially have the wrong size for the
+  // border too.
+  aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
+
+  nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea,
+                                    aStyleBorder, aSkipSides, imgRenderer);
+  *aDrawResult = DrawResult::SUCCESS;
+  return Some(renderer);
+}
+
+DrawResult
+nsCSSBorderImageRenderer::DrawBorderImage(nsPresContext* aPresContext,
+                                          nsRenderingContext& aRenderingContext,
+                                          nsIFrame* aForFrame,
+                                          const nsRect& aDirtyRect)
+{
+  // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
+  // in case we need it.
+  gfxContextAutoSaveRestore autoSR;
+
+  if (!mClip.IsEmpty()) {
+    autoSR.EnsureSaved(aRenderingContext.ThebesContext());
+    aRenderingContext.ThebesContext()->
+      Clip(NSRectToSnappedRect(mClip,
+                               aForFrame->PresContext()->AppUnitsPerDevPixel(),
+                               *aRenderingContext.GetDrawTarget()));
+  }
+
+  // intrinsicSize.CanComputeConcreteSize() return false means we can not
+  // read intrinsic size from aStyleBorder.mBorderImageSource.
+  // In this condition, we pass imageSize(a resolved size comes from
+  // default sizing algorithm) to renderer as the viewport size.
+  CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
+  Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
+                                  Nothing() : Some(mImageSize);
+  bool hasIntrinsicRatio = intrinsicSize.HasRatio();
+  mImageRenderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
+
+  // These helper tables recharacterize the 'slice' and 'width' margins
+  // in a more convenient form: they are the x/y/width/height coords
+  // required for various bands of the border, and they have been transformed
+  // to be relative to the innerRect (for 'slice') or the page (for 'border').
+  enum {
+    LEFT, MIDDLE, RIGHT,
+    TOP = LEFT, BOTTOM = RIGHT
+  };
+  const nscoord borderX[3] = {
+    mArea.x + 0,
+    mArea.x + mWidths.left,
+    mArea.x + mArea.width - mWidths.right,
+  };
+  const nscoord borderY[3] = {
+    mArea.y + 0,
+    mArea.y + mWidths.top,
+    mArea.y + mArea.height - mWidths.bottom,
+  };
+  const nscoord borderWidth[3] = {
+    mWidths.left,
+    mArea.width - mWidths.left - mWidths.right,
+    mWidths.right,
+  };
+  const nscoord borderHeight[3] = {
+    mWidths.top,
+    mArea.height - mWidths.top - mWidths.bottom,
+    mWidths.bottom,
+  };
+  const int32_t sliceX[3] = {
+    0,
+    mSlice.left,
+    mImageSize.width - mSlice.right,
+  };
+  const int32_t sliceY[3] = {
+    0,
+    mSlice.top,
+    mImageSize.height - mSlice.bottom,
+  };
+  const int32_t sliceWidth[3] = {
+    mSlice.left,
+    std::max(mImageSize.width - mSlice.left - mSlice.right, 0),
+    mSlice.right,
+  };
+  const int32_t sliceHeight[3] = {
+    mSlice.top,
+    std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0),
+    mSlice.bottom,
+  };
+
+  DrawResult result = DrawResult::SUCCESS;
+
+  for (int i = LEFT; i <= RIGHT; i++) {
+    for (int j = TOP; j <= BOTTOM; j++) {
+      uint8_t fillStyleH, fillStyleV;
+      nsSize unitSize;
+
+      if (i == MIDDLE && j == MIDDLE) {
+        // Discard the middle portion unless set to fill.
+        if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL == mFill) {
+          continue;
+        }
+
+        NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL == mFill,
+                     "Unexpected border image fill");
+
+        // css-background:
+        //     The middle image's width is scaled by the same factor as the
+        //     top image unless that factor is zero or infinity, in which
+        //     case the scaling factor of the bottom is substituted, and
+        //     failing that, the width is not scaled. The height of the
+        //     middle image is scaled by the same factor as the left image
+        //     unless that factor is zero or infinity, in which case the
+        //     scaling factor of the right image is substituted, and failing
+        //     that, the height is not scaled.
+        gfxFloat hFactor, vFactor;
+
+        if (0 < mWidths.left && 0 < mSlice.left)
+          vFactor = gfxFloat(mWidths.left) / mSlice.left;
+        else if (0 < mWidths.right && 0 < mSlice.right)
+          vFactor = gfxFloat(mWidths.right) / mSlice.right;
+        else
+          vFactor = 1;
+
+        if (0 < mWidths.top && 0 < mSlice.top)
+          hFactor = gfxFloat(mWidths.top) / mSlice.top;
+        else if (0 < mWidths.bottom && 0 < mSlice.bottom)
+          hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom;
+        else
+          hFactor = 1;
+
+        unitSize.width = sliceWidth[i] * hFactor;
+        unitSize.height = sliceHeight[j] * vFactor;
+        fillStyleH = mRepeatModeHorizontal;
+        fillStyleV = mRepeatModeVertical;
+
+      } else if (i == MIDDLE) { // top, bottom
+        // Sides are always stretched to the thickness of their border,
+        // and stretched proportionately on the other axis.
+        gfxFloat factor;
+        if (0 < borderHeight[j] && 0 < sliceHeight[j])
+          factor = gfxFloat(borderHeight[j]) / sliceHeight[j];
+        else
+          factor = 1;
+
+        unitSize.width = sliceWidth[i] * factor;
+        unitSize.height = borderHeight[j];
+        fillStyleH = mRepeatModeHorizontal;
+        fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+
+      } else if (j == MIDDLE) { // left, right
+        gfxFloat factor;
+        if (0 < borderWidth[i] && 0 < sliceWidth[i])
+          factor = gfxFloat(borderWidth[i]) / sliceWidth[i];
+        else
+          factor = 1;
+
+        unitSize.width = borderWidth[i];
+        unitSize.height = sliceHeight[j] * factor;
+        fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+        fillStyleV = mRepeatModeVertical;
+
+      } else {
+        // Corners are always stretched to fit the corner.
+        unitSize.width = borderWidth[i];
+        unitSize.height = borderHeight[j];
+        fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+        fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
+      }
+
+      nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
+      nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
+      if (subArea.IsEmpty())
+        continue;
+
+      nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
+      result &=
+        mImageRenderer.DrawBorderImageComponent(aPresContext,
+                                                 aRenderingContext, aDirtyRect,
+                                                 destArea, CSSIntRect(intSubArea.x,
+                                                                      intSubArea.y,
+                                                                      intSubArea.width,
+                                                                      intSubArea.height),
+                                                 fillStyleH, fillStyleV,
+                                                 unitSize, j * (RIGHT + 1) + i,
+                                                 svgViewportSize, hasIntrinsicRatio);
+    }
+  }
+
+  return result;
+}
+
+nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(nsIFrame* aForFrame,
+                                                   const nsRect& aBorderArea,
+                                                   const nsStyleBorder& aStyleBorder,
+                                                   Sides aSkipSides,
+                                                   const nsImageRenderer& aImageRenderer)
+  : mImageRenderer(aImageRenderer)
+{
+
+  // Determine the border image area, which by default corresponds to the
+  // border box but can be modified by 'border-image-outset'.
+  // Note that 'border-radius' do not apply to 'border-image' borders per
+  // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
+  nsMargin borderWidths(aStyleBorder.GetComputedBorder());
+  mImageOutset = aStyleBorder.GetImageOutset();
+  if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
+    mArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
+                                         aSkipSides, &aStyleBorder);
+    if (mArea.IsEqualEdges(aBorderArea)) {
+      // No need for a clip, just skip the sides we don't want.
+      borderWidths.ApplySkipSides(aSkipSides);
+      mImageOutset.ApplySkipSides(aSkipSides);
+      mArea.Inflate(mImageOutset);
+    } else {
+      // We're drawing borders around the joined continuation boxes so we need
+      // to clip that to the slice that we want for this frame.
+      mArea.Inflate(mImageOutset);
+      mImageOutset.ApplySkipSides(aSkipSides);
+      mClip = aBorderArea;
+      mClip.Inflate(mImageOutset);
+    }
+  } else {
+    mArea = aBorderArea;
+    mArea.Inflate(mImageOutset);
+  }
+
+  // Calculate the image size used to compute slice points.
+  CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
+  mImageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
+                                                    intrinsicSize,
+                                                    mArea.Size());
+  mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize);
+
+  // Compute the used values of 'border-image-slice' and 'border-image-width';
+  // we do them together because the latter can depend on the former.
+  nsMargin slice;
+  nsMargin border;
+  NS_FOR_CSS_SIDES(s) {
+    nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
+    int32_t imgDimension = SideIsVertical(s)
+                           ? mImageSize.width : mImageSize.height;
+    nscoord borderDimension = SideIsVertical(s)
+                           ? mArea.width : mArea.height;
+    double value;
+    switch (coord.GetUnit()) {
+      case eStyleUnit_Percent:
+        value = coord.GetPercentValue() * imgDimension;
+        break;
+      case eStyleUnit_Factor:
+        value = nsPresContext::CSSPixelsToAppUnits(
+          NS_lround(coord.GetFactorValue()));
+        break;
+      default:
+        NS_NOTREACHED("unexpected CSS unit for image slice");
+        value = 0;
+        break;
+    }
+    if (value < 0)
+      value = 0;
+    if (value > imgDimension)
+      value = imgDimension;
+    mSlice.Side(s) = value;
+
+    coord = aStyleBorder.mBorderImageWidth.Get(s);
+    switch (coord.GetUnit()) {
+      case eStyleUnit_Coord: // absolute dimension
+        value = coord.GetCoordValue();
+        break;
+      case eStyleUnit_Percent:
+        value = coord.GetPercentValue() * borderDimension;
+        break;
+      case eStyleUnit_Factor:
+        value = coord.GetFactorValue() * borderWidths.Side(s);
+        break;
+      case eStyleUnit_Auto:  // same as the slice value, in CSS pixels
+        value = mSlice.Side(s);
+        break;
+      default:
+        NS_NOTREACHED("unexpected CSS unit for border image area division");
+        value = 0;
+        break;
+    }
+    // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
+    // because we expect value to be non-negative.
+    MOZ_ASSERT(value >= 0);
+    mWidths.Side(s) = NSToCoordRoundWithClamp(value);
+    MOZ_ASSERT(mWidths.Side(s) >= 0);
+  }
+
+  // "If two opposite border-image-width offsets are large enough that they
+  // overlap, their used values are proportionately reduced until they no
+  // longer overlap."
+  uint32_t combinedBorderWidth = uint32_t(mWidths.left) +
+                                 uint32_t(mWidths.right);
+  double scaleX = combinedBorderWidth > uint32_t(mArea.width)
+                  ? mArea.width / double(combinedBorderWidth)
+                  : 1.0;
+  uint32_t combinedBorderHeight = uint32_t(mWidths.top) +
+                                  uint32_t(mWidths.bottom);
+  double scaleY = combinedBorderHeight > uint32_t(mArea.height)
+                  ? mArea.height / double(combinedBorderHeight)
+                  : 1.0;
+  double scale = std::min(scaleX, scaleY);
+  if (scale < 1.0) {
+    mWidths.left *= scale;
+    mWidths.right *= scale;
+    mWidths.top *= scale;
+    mWidths.bottom *= scale;
+    NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width &&
+                 mWidths.top + mWidths.bottom <= mArea.height,
+                 "rounding error in width reduction???");
+  }
+
+  mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeatH;
+  mRepeatModeVertical = aStyleBorder.mBorderImageRepeatV;
+  mFill = aStyleBorder.mBorderImageFill;
+}
--- a/layout/painting/nsCSSRenderingBorders.h
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -262,16 +262,55 @@ private:
   // CompositeColors.
   void DrawNoCompositeColorSolidBorder();
 
   // Draw a solid border that has no border radius (i.e. is rectangular) and
   // uses CompositeColors.
   void DrawRectangularCompositeColors();
 };
 
+class nsCSSBorderImageRenderer final
+{
+  typedef mozilla::nsImageRenderer nsImageRenderer;
+public:
+  static mozilla::Maybe<nsCSSBorderImageRenderer>
+  CreateBorderImageRenderer(nsPresContext* aPresContext,
+                            nsIFrame* aForFrame,
+                            const nsRect& aBorderArea,
+                            const nsStyleBorder& aStyleBorder,
+                            const nsRect& aDirtyRect,
+                            nsIFrame::Sides aSkipSides,
+                            uint32_t aFlags,
+                            mozilla::image::DrawResult* aDrawResult);
+
+  mozilla::image::DrawResult
+  DrawBorderImage(nsPresContext* aPresContext,
+                  nsRenderingContext& aRenderingContext,
+                  nsIFrame* aForFrame,
+                  const nsRect& aDirtyRect);
+
+private:
+  nsCSSBorderImageRenderer(nsIFrame* aForFrame,
+                           const nsRect& aBorderArea,
+                           const nsStyleBorder& aStyleBorder,
+                           nsIFrame::Sides aSkipSides,
+                           const nsImageRenderer& aImageRenderer);
+
+  nsImageRenderer mImageRenderer;
+  nsSize mImageSize;
+  nsMargin mSlice;
+  nsMargin mWidths;
+  nsMargin mImageOutset;
+  nsRect mArea;
+  nsRect mClip;
+  uint8_t mRepeatModeHorizontal;
+  uint8_t mRepeatModeVertical;
+  uint8_t mFill;
+};
+
 namespace mozilla {
 #ifdef DEBUG_NEW_BORDERS
 #include <stdarg.h>
 
 static inline void PrintAsString(const mozilla::gfx::Point& p) {
   fprintf (stderr, "[%f,%f]", p.x, p.y);
 }