Bug 1221840. Support repeating images in 1 axis. r=seth
authorMason Chang <mchang@mozilla.com>
Mon, 23 Nov 2015 08:17:35 -0800
changeset 273860 cae5c087063d54c80a13fc3f4914320a17febd97
parent 273859 16e98ad8c97b2232a228e67cda6d76f7cd66dab2
child 273861 cd5c09ba9561cd035827aa2cb75567fe91a4d2bd
push id29715
push userkwierso@gmail.com
push dateTue, 24 Nov 2015 21:54:25 +0000
treeherdermozilla-central@d9243e369c22 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseth
bugs1221840
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1221840. Support repeating images in 1 axis. r=seth
gfx/2d/DrawTargetCG.cpp
gfx/2d/DrawTargetD2D.cpp
gfx/2d/DrawTargetD2D1.cpp
gfx/2d/DrawTargetSkia.cpp
gfx/2d/HelpersCairo.h
gfx/2d/HelpersD2D.h
gfx/2d/HelpersSkia.h
gfx/2d/Types.h
gfx/thebes/gfxDrawable.cpp
gfx/thebes/gfxDrawable.h
gfx/thebes/gfxUtils.cpp
image/ImageRegion.h
image/imgFrame.cpp
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/gfx/2d/DrawTargetCG.cpp
+++ b/gfx/2d/DrawTargetCG.cpp
@@ -709,17 +709,17 @@ DrawGradient(CGColorSpaceRef aColorSpace
       CGPoint endPoint   = { pat.mEnd.x,   pat.mEnd.y };
 
       // Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?)
       //if (startPoint.x == endPoint.x && startPoint.y == endPoint.y)
       //  return;
 
       CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
                                   kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
-    } else if (stops->mExtend == ExtendMode::REPEAT || stops->mExtend == ExtendMode::REFLECT) {
+    } else {
       DrawLinearRepeatingGradient(aColorSpace, cg, pat, extents, stops->mExtend == ExtendMode::REFLECT);
     }
   } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
     const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
     CGAffineTransform patternMatrix = GfxMatrixToCGAffineTransform(pat.mMatrix);
     CGContextConcatCTM(cg, patternMatrix);
     CGRect extents = CGRectApplyAffineTransform(aExtents, CGAffineTransformInvert(patternMatrix));
     GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
@@ -729,17 +729,17 @@ DrawGradient(CGColorSpaceRef aColorSpace
       CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y };
       CGFloat startRadius = pat.mRadius1;
       CGPoint endCenter   = { pat.mCenter2.x, pat.mCenter2.y };
       CGFloat endRadius   = pat.mRadius2;
 
       //XXX: are there degenerate radial gradients that we should avoid drawing?
       CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
                                   kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
-    } else if (stops->mExtend == ExtendMode::REPEAT || stops->mExtend == ExtendMode::REFLECT) {
+    } else {
       DrawRadialRepeatingGradient(aColorSpace, cg, pat, extents, stops->mExtend == ExtendMode::REFLECT);
     }
   } else {
     assert(0);
   }
 
 }
 
@@ -770,18 +770,24 @@ static bool
 isGradient(const Pattern &aPattern)
 {
   return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT;
 }
 
 static bool
 isNonRepeatingSurface(const Pattern& aPattern)
 {
-  return aPattern.GetType() == PatternType::SURFACE &&
-    static_cast<const SurfacePattern&>(aPattern).mExtendMode != ExtendMode::REPEAT;
+  if (aPattern.GetType() != PatternType::SURFACE) {
+    return false;
+  }
+
+  const SurfacePattern& surfacePattern = static_cast<const SurfacePattern&>(aPattern);
+  return surfacePattern.mExtendMode != ExtendMode::REPEAT &&
+         surfacePattern.mExtendMode != ExtendMode::REPEAT_X &&
+         surfacePattern.mExtendMode != ExtendMode::REPEAT_Y;
 }
 
 /* CoreGraphics patterns ignore the userspace transform so
  * we need to multiply it in */
 static CGPatternRef
 CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
 {
   const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
@@ -810,16 +816,25 @@ CreateCGPattern(const Pattern &aPattern,
       // this is done to avoid pixel-cracking along pattern boundaries
       // (see https://bugs.webkit.org/show_bug.cgi?id=53055)
       // typedef enum {
       //    wkPatternTilingNoDistortion,
       //    wkPatternTilingConstantSpacingMinimalDistortion,
       //    wkPatternTilingConstantSpacing
       // } wkPatternTiling;
       // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int);
+      break;
+    case ExtendMode::REPEAT_X:
+      xStep = static_cast<CGFloat>(CGImageGetWidth(image));
+      yStep = static_cast<CGFloat>(1 << 22);
+      break;
+    case ExtendMode::REPEAT_Y:
+      yStep = static_cast<CGFloat>(CGImageGetHeight(image));
+      xStep = static_cast<CGFloat>(1 << 22);
+      break;
   }
 
   //XXX: We should be using CGContextDrawTiledImage when we can. Even though it
   // creates a pattern, it seems to go down a faster path than using a delegate
   // like we do below
   CGRect bounds = {
     {0, 0,},
     {static_cast<CGFloat>(CGImageGetWidth(image)), static_cast<CGFloat>(CGImageGetHeight(image))}
--- a/gfx/2d/DrawTargetD2D.cpp
+++ b/gfx/2d/DrawTargetD2D.cpp
@@ -1313,17 +1313,17 @@ DrawTargetD2D::CreateGradientStops(Gradi
     stops[i].position = rawStops[i].offset;
     stops[i].color = D2DColor(rawStops[i].color);
   }
 
   RefPtr<ID2D1GradientStopCollection> stopCollection;
 
   HRESULT hr =
     mRT->CreateGradientStopCollection(stops, aNumStops,
-                                      D2D1_GAMMA_2_2, D2DExtend(aExtendMode),
+                                      D2D1_GAMMA_2_2, D2DExtend(aExtendMode, Axis::BOTH),
                                       getter_AddRefs(stopCollection));
   delete [] stops;
 
   if (FAILED(hr)) {
     gfxWarning() << "Failed to create GradientStopCollection. Code: " << hexa(hr);
     return nullptr;
   }
 
@@ -2440,19 +2440,21 @@ DrawTargetD2D::CreateBrushForPattern(con
           RefPtr<ID2D1SolidColorBrush> colBrush;
           mRT->CreateSolidColorBrush(D2D1::ColorF(0, 0), getter_AddRefs(colBrush));
           return colBrush.forget();
         }
       }
       break;
     }
 
+    D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
+    D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
     HRESULT hr = mRT->CreateBitmapBrush(bitmap,
-                           D2D1::BitmapBrushProperties(D2DExtend(pat->mExtendMode),
-                                                       D2DExtend(pat->mExtendMode),
+                           D2D1::BitmapBrushProperties(xRepeat,
+                                                       yRepeat,
                                                        D2DFilter(pat->mFilter)),
                            D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
                            getter_AddRefs(bmBrush));
     if (FAILED(hr)) {
       gfxCriticalError() << "[D2D] 1CreateBitmapBrush failure: " << hexa(hr);
       return nullptr;
     }
     return bmBrush.forget();
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -800,17 +800,17 @@ DrawTargetD2D1::CreateGradientStops(Grad
     stops[i].position = rawStops[i].offset;
     stops[i].color = D2DColor(rawStops[i].color);
   }
 
   RefPtr<ID2D1GradientStopCollection> stopCollection;
 
   HRESULT hr =
     mDC->CreateGradientStopCollection(stops, aNumStops,
-                                      D2D1_GAMMA_2_2, D2DExtend(aExtendMode),
+                                      D2D1_GAMMA_2_2, D2DExtend(aExtendMode, Axis::BOTH),
                                       getter_AddRefs(stopCollection));
   delete [] stops;
 
   if (FAILED(hr)) {
     gfxWarning() << *this << ": Failed to create GradientStopCollection. Code: " << hexa(hr);
     return nullptr;
   }
 
@@ -1463,20 +1463,25 @@ DrawTargetD2D1::CreateBrushForPattern(co
     if (!image) {
       return CreateTransparentBlackBrush();
     }
 
     if (pat->mSamplingRect.IsEmpty()) {
       RefPtr<ID2D1Bitmap> bitmap;
       image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
       if (bitmap) {
+        /**
+         * Create the brush with the proper repeat modes.
+         */
         RefPtr<ID2D1BitmapBrush> bitmapBrush;
+        D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
+        D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
+
         mDC->CreateBitmapBrush(bitmap,
-                               D2D1::BitmapBrushProperties(D2DExtend(pat->mExtendMode),
-                                                           D2DExtend(pat->mExtendMode),
+                               D2D1::BitmapBrushProperties(xRepeat, yRepeat,
                                                            D2DFilter(pat->mFilter)),
                                D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
                                getter_AddRefs(bitmapBrush));
         if (!bitmapBrush) {
           gfxWarning() << "Couldn't create bitmap brush!";
           return CreateTransparentBlackBrush();
         }
         return bitmapBrush.forget();
@@ -1490,20 +1495,24 @@ DrawTargetD2D1::CreateBrushForPattern(co
                                    Float(pat->mSurface->GetSize().height));
     } else if (pat->mSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
       samplingBounds = D2DRect(pat->mSamplingRect);
       mat.PreTranslate(pat->mSamplingRect.x, pat->mSamplingRect.y);
     } else {
       // We will do a partial upload of the sampling restricted area from GetImageForSurface.
       samplingBounds = D2D1::RectF(0, 0, pat->mSamplingRect.width, pat->mSamplingRect.height);
     }
+
+    D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
+    D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
+
     mDC->CreateImageBrush(image,
                           D2D1::ImageBrushProperties(samplingBounds,
-                                                     D2DExtend(pat->mExtendMode),
-                                                     D2DExtend(pat->mExtendMode),
+                                                     xRepeat,
+                                                     yRepeat,
                                                      D2DInterpolationMode(pat->mFilter)),
                           D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
                           getter_AddRefs(imageBrush));
 
     if (!imageBrush) {
       gfxWarning() << "Couldn't create image brush!";
       return CreateTransparentBlackBrush();
     }
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -181,17 +181,17 @@ SetPaintPattern(SkPaint& aPaint, const P
     case PatternType::COLOR: {
       Color color = static_cast<const ColorPattern&>(aPattern).mColor;
       aPaint.setColor(ColorToSkColor(color, aAlpha));
       break;
     }
     case PatternType::LINEAR_GRADIENT: {
       const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
       GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get());
-      SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode);
+      SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
 
       if (stops->mCount >= 2) {
         SkPoint points[2];
         points[0] = SkPoint::Make(SkFloatToScalar(pat.mBegin.x), SkFloatToScalar(pat.mBegin.y));
         points[1] = SkPoint::Make(SkFloatToScalar(pat.mEnd.x), SkFloatToScalar(pat.mEnd.y));
 
         SkShader* shader = SkGradientShader::CreateLinear(points,
                                                           &stops->mColors.front(),
@@ -210,17 +210,17 @@ SetPaintPattern(SkPaint& aPaint, const P
       } else {
         aPaint.setColor(SkColorSetARGB(0, 0, 0, 0));
       }
       break;
     }
     case PatternType::RADIAL_GRADIENT: {
       const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
       GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get());
-      SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode);
+      SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
 
       if (stops->mCount >= 2) {
         SkPoint points[2];
         points[0] = SkPoint::Make(SkFloatToScalar(pat.mCenter1.x), SkFloatToScalar(pat.mCenter1.y));
         points[1] = SkPoint::Make(SkFloatToScalar(pat.mCenter2.x), SkFloatToScalar(pat.mCenter2.y));
 
         SkShader* shader = SkGradientShader::CreateTwoPointConical(points[0],
                                                                    SkFloatToScalar(pat.mRadius1),
@@ -252,18 +252,20 @@ SetPaintPattern(SkPaint& aPaint, const P
       GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
 
       if (!pat.mSamplingRect.IsEmpty()) {
         SkIRect rect = IntRectToSkIRect(pat.mSamplingRect);
         bitmap.extractSubset(&bitmap, rect);
         mat.preTranslate(rect.x(), rect.y());
       }
 
-      SkShader::TileMode mode = ExtendModeToTileMode(pat.mExtendMode);
-      SkShader* shader = SkShader::CreateBitmapShader(bitmap, mode, mode);
+      SkShader::TileMode xTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS);
+      SkShader::TileMode yTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS);
+
+      SkShader* shader = SkShader::CreateBitmapShader(bitmap, xTileMode, yTileMode);
       SkShader* matrixShader = SkShader::CreateLocalMatrixShader(shader, mat);
       SkSafeUnref(shader);
       SkSafeUnref(aPaint.setShader(matrixShader));
       if (pat.mFilter == Filter::POINT) {
         aPaint.setFilterLevel(SkPaint::kNone_FilterLevel);
       }
       break;
     }
--- a/gfx/2d/HelpersCairo.h
+++ b/gfx/2d/HelpersCairo.h
@@ -128,16 +128,20 @@ GfxFilterToCairoFilter(Filter filter)
 
 static inline cairo_extend_t
 GfxExtendToCairoExtend(ExtendMode extend)
 {
   switch (extend)
   {
     case ExtendMode::CLAMP:
       return CAIRO_EXTEND_PAD;
+    // Cairo doesn't support tiling in only 1 direction,
+    // So we have to fallback and tile in both.
+    case ExtendMode::REPEAT_X:
+    case ExtendMode::REPEAT_Y:
     case ExtendMode::REPEAT:
       return CAIRO_EXTEND_REPEAT;
     case ExtendMode::REFLECT:
       return CAIRO_EXTEND_REFLECT;
   }
 
   return CAIRO_EXTEND_PAD;
 }
--- a/gfx/2d/HelpersD2D.h
+++ b/gfx/2d/HelpersD2D.h
@@ -40,23 +40,37 @@ static inline D2D1_SIZE_U D2DIntSize(con
 }
 
 template <typename T>
 static inline D2D1_RECT_F D2DRect(const T &aRect)
 {
   return D2D1::RectF(aRect.x, aRect.y, aRect.XMost(), aRect.YMost());
 }
 
-static inline D2D1_EXTEND_MODE D2DExtend(ExtendMode aExtendMode)
+static inline D2D1_EXTEND_MODE D2DExtend(ExtendMode aExtendMode, Axis aAxis)
 {
   D2D1_EXTEND_MODE extend;
   switch (aExtendMode) {
   case ExtendMode::REPEAT:
     extend = D2D1_EXTEND_MODE_WRAP;
     break;
+  case ExtendMode::REPEAT_X:
+  {
+    extend = aAxis == Axis::X_AXIS
+             ? D2D1_EXTEND_MODE_WRAP
+             : D2D1_EXTEND_MODE_CLAMP;
+    break;
+  }
+  case ExtendMode::REPEAT_Y:
+  {
+    extend = aAxis == Axis::Y_AXIS
+             ? D2D1_EXTEND_MODE_WRAP
+             : D2D1_EXTEND_MODE_CLAMP;
+    break;
+  }
   case ExtendMode::REFLECT:
     extend = D2D1_EXTEND_MODE_MIRROR;
     break;
   default:
     extend = D2D1_EXTEND_MODE_CLAMP;
   }
 
   return extend;
--- a/gfx/2d/HelpersSkia.h
+++ b/gfx/2d/HelpersSkia.h
@@ -286,26 +286,38 @@ SkPointToPoint(const SkPoint &aPoint)
 static inline Rect
 SkRectToRect(const SkRect &aRect)
 {
   return Rect(SkScalarToFloat(aRect.x()), SkScalarToFloat(aRect.y()),
               SkScalarToFloat(aRect.width()), SkScalarToFloat(aRect.height()));
 }
 
 static inline SkShader::TileMode
-ExtendModeToTileMode(ExtendMode aMode)
+ExtendModeToTileMode(ExtendMode aMode, Axis aAxis)
 {
   switch (aMode)
   {
     case ExtendMode::CLAMP:
       return SkShader::kClamp_TileMode;
     case ExtendMode::REPEAT:
       return SkShader::kRepeat_TileMode;
     case ExtendMode::REFLECT:
       return SkShader::kMirror_TileMode;
+    case ExtendMode::REPEAT_X:
+    {
+      return aAxis == Axis::X_AXIS
+             ? SkShader::kRepeat_TileMode
+             : SkShader::kClamp_TileMode;
+    }
+    case ExtendMode::REPEAT_Y:
+    {
+      return aAxis == Axis::Y_AXIS
+             ? SkShader::kRepeat_TileMode
+             : SkShader::kClamp_TileMode;
+    }
   }
   return SkShader::kClamp_TileMode;
 }
 
 static inline SkPaint::Hinting
 GfxHintingToSkiaHinting(FontHinting aHinting)
 {
   switch (aHinting) {
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -197,20 +197,28 @@ enum class CompositionOp : int8_t {
   OP_EXCLUSION,
   OP_HUE,
   OP_SATURATION,
   OP_COLOR,
   OP_LUMINOSITY,
   OP_COUNT
 };
 
+enum class Axis : int8_t {
+  X_AXIS,
+  Y_AXIS,
+  BOTH
+};
+
 enum class ExtendMode : int8_t {
-  CLAMP,
-  REPEAT,
-  REFLECT
+  CLAMP,    // Do not repeat
+  REPEAT,   // Repeat in both axis
+  REPEAT_X, // Only X axis
+  REPEAT_Y, // Only Y axis
+  REFLECT   // Mirror the image
 };
 
 enum class FillRule : int8_t {
   FILL_WINDING,
   FILL_EVEN_ODD
 };
 
 enum class AntialiasMode : int8_t {
--- a/gfx/thebes/gfxDrawable.cpp
+++ b/gfx/thebes/gfxDrawable.cpp
@@ -28,17 +28,17 @@ gfxSurfaceDrawable::gfxSurfaceDrawable(S
     gfxWarning() << "Creating gfxSurfaceDrawable with null SourceSurface";
   }
 }
 
 bool
 gfxSurfaceDrawable::DrawWithSamplingRect(gfxContext* aContext,
                                          const gfxRect& aFillRect,
                                          const gfxRect& aSamplingRect,
-                                         bool aRepeat,
+                                         ExtendMode aExtendMode,
                                          const Filter& aFilter,
                                          gfxFloat aOpacity)
 {
   if (!mSourceSurface) {
     return true;
   }
 
   // When drawing with CLAMP we can expand the sampling rect to the nearest pixel
@@ -47,55 +47,50 @@ gfxSurfaceDrawable::DrawWithSamplingRect
   samplingRect.RoundOut();
   IntRect intRect(samplingRect.x, samplingRect.y, samplingRect.width, samplingRect.height);
 
   IntSize size = mSourceSurface->GetSize();
   if (!IntRect(0, 0, size.width, size.height).Contains(intRect)) {
     return false;
   }
 
-  DrawInternal(aContext, aFillRect, intRect, false, aFilter, aOpacity, gfxMatrix());
+  DrawInternal(aContext, aFillRect, intRect, ExtendMode::CLAMP, aFilter, aOpacity, gfxMatrix());
   return true;
 }
 
 bool
 gfxSurfaceDrawable::Draw(gfxContext* aContext,
                          const gfxRect& aFillRect,
-                         bool aRepeat,
+                         ExtendMode aExtendMode,
                          const Filter& aFilter,
                          gfxFloat aOpacity,
                          const gfxMatrix& aTransform)
+
 {
   if (!mSourceSurface) {
     return true;
   }
 
-  DrawInternal(aContext, aFillRect, IntRect(), aRepeat, aFilter, aOpacity, aTransform);
+  DrawInternal(aContext, aFillRect, IntRect(), aExtendMode, aFilter, aOpacity, aTransform);
   return true;
 }
 
 void
 gfxSurfaceDrawable::DrawInternal(gfxContext* aContext,
                                  const gfxRect& aFillRect,
                                  const IntRect& aSamplingRect,
-                                 bool aRepeat,
+                                 ExtendMode aExtendMode,
                                  const Filter& aFilter,
                                  gfxFloat aOpacity,
                                  const gfxMatrix& aTransform)
 {
-    ExtendMode extend = ExtendMode::CLAMP;
-
-    if (aRepeat) {
-        extend = ExtendMode::REPEAT;
-    }
-
     Matrix patternTransform = ToMatrix(aTransform * mTransform);
     patternTransform.Invert();
 
-    SurfacePattern pattern(mSourceSurface, extend,
+    SurfacePattern pattern(mSourceSurface, aExtendMode,
                            patternTransform, aFilter, aSamplingRect);
 
     Rect fillRect = ToRect(aFillRect);
     DrawTarget* dt = aContext->GetDrawTarget();
 
     if (aContext->CurrentOp() == CompositionOp::OP_SOURCE &&
         aOpacity == 1.0) {
         // Emulate cairo operator source which is bound by mask!
@@ -122,40 +117,53 @@ gfxCallbackDrawable::MakeSurfaceDrawable
         gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR_ALPHA);
     RefPtr<DrawTarget> dt =
         gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(mSize,
                                                                      format);
     if (!dt)
         return nullptr;
 
     RefPtr<gfxContext> ctx = new gfxContext(dt);
-    Draw(ctx, gfxRect(0, 0, mSize.width, mSize.height), false, aFilter);
+    Draw(ctx, gfxRect(0, 0, mSize.width, mSize.height), ExtendMode::CLAMP, aFilter);
 
     RefPtr<SourceSurface> surface = dt->Snapshot();
     if (surface) {
         RefPtr<gfxSurfaceDrawable> drawable = new gfxSurfaceDrawable(surface, mSize);
         return drawable.forget();
     }
     return nullptr;
 }
 
+static bool
+IsRepeatingExtendMode(ExtendMode aExtendMode)
+{
+  switch (aExtendMode) {
+  case ExtendMode::REPEAT:
+  case ExtendMode::REPEAT_X:
+  case ExtendMode::REPEAT_Y:
+    return true;
+  default:
+    return false;
+  }
+}
+
 bool
 gfxCallbackDrawable::Draw(gfxContext* aContext,
                           const gfxRect& aFillRect,
-                          bool aRepeat,
+                          ExtendMode aExtendMode,
                           const Filter& aFilter,
                           gfxFloat aOpacity,
                           const gfxMatrix& aTransform)
 {
-    if ((aRepeat || aOpacity != 1.0) && !mSurfaceDrawable) {
+    if ((IsRepeatingExtendMode(aExtendMode) || aOpacity != 1.0) && !mSurfaceDrawable) {
         mSurfaceDrawable = MakeSurfaceDrawable(aFilter);
     }
 
     if (mSurfaceDrawable)
-        return mSurfaceDrawable->Draw(aContext, aFillRect, aRepeat, aFilter,
+        return mSurfaceDrawable->Draw(aContext, aFillRect, aExtendMode, aFilter,
                                       aOpacity, aTransform);
 
     if (mCallback)
         return (*mCallback)(aContext, aFillRect, aFilter, aTransform);
 
     return false;
 }
 
@@ -179,17 +187,17 @@ public:
 
     virtual ~DrawingCallbackFromDrawable() {}
 
     virtual bool operator()(gfxContext* aContext,
                               const gfxRect& aFillRect,
                               const Filter& aFilter,
                               const gfxMatrix& aTransform = gfxMatrix())
     {
-        return mDrawable->Draw(aContext, aFillRect, false, aFilter, 1.0,
+        return mDrawable->Draw(aContext, aFillRect, ExtendMode::CLAMP, aFilter, 1.0,
                                aTransform);
     }
 private:
     RefPtr<gfxDrawable> mDrawable;
 };
 
 already_AddRefed<gfxCallbackDrawable>
 gfxPatternDrawable::MakeCallbackDrawable()
@@ -199,36 +207,36 @@ gfxPatternDrawable::MakeCallbackDrawable
     RefPtr<gfxCallbackDrawable> callbackDrawable =
         new gfxCallbackDrawable(callback, mSize);
     return callbackDrawable.forget();
 }
 
 bool
 gfxPatternDrawable::Draw(gfxContext* aContext,
                          const gfxRect& aFillRect,
-                         bool aRepeat,
+                         ExtendMode aExtendMode,
                          const Filter& aFilter,
                          gfxFloat aOpacity,
                          const gfxMatrix& aTransform)
 {
     DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
 
     if (!mPattern)
         return false;
 
-    if (aRepeat) {
+    if (IsRepeatingExtendMode(aExtendMode)) {
         // We can't use mPattern directly: We want our repeated tiles to have
         // the size mSize, which might not be the case in mPattern.
         // So we need to draw mPattern into a surface of size mSize, create
         // a pattern from the surface and draw that pattern.
         // gfxCallbackDrawable and gfxSurfaceDrawable already know how to do
         // those things, so we use them here. Drawing mPattern into the surface
         // will happen through this Draw() method with aRepeat = false.
         RefPtr<gfxCallbackDrawable> callbackDrawable = MakeCallbackDrawable();
-        return callbackDrawable->Draw(aContext, aFillRect, true, aFilter,
+        return callbackDrawable->Draw(aContext, aFillRect, aExtendMode, aFilter,
                                       aOpacity, aTransform);
     }
 
     gfxMatrix oldMatrix = mPattern->GetMatrix();
     mPattern->SetMatrix(aTransform * oldMatrix);
     DrawOptions drawOptions(aOpacity);
     aDrawTarget.FillRect(ToRect(aFillRect),
                          *mPattern->GetPattern(&aDrawTarget), drawOptions);
--- a/gfx/thebes/gfxDrawable.h
+++ b/gfx/thebes/gfxDrawable.h
@@ -5,16 +5,17 @@
 
 #ifndef GFX_DRAWABLE_H
 #define GFX_DRAWABLE_H
 
 #include "nsAutoPtr.h"
 #include "gfxRect.h"
 #include "gfxMatrix.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
 
 class gfxContext;
 class gfxPattern;
 
 /**
  * gfxDrawable
  * An Interface representing something that has an intrinsic size and can draw
  * itself repeatedly.
@@ -29,24 +30,25 @@ public:
      * Draw into aContext filling aFillRect, possibly repeating, using aFilter.
      * aTransform is a userspace to "image"space matrix. For example, if Draw
      * draws using a gfxPattern, this is the matrix that should be set on the
      * pattern prior to rendering it.
      *  @return whether drawing was successful
      */
     virtual bool Draw(gfxContext* aContext,
                         const gfxRect& aFillRect,
-                        bool aRepeat,
+                        mozilla::gfx::ExtendMode aExtendMode,
                         const mozilla::gfx::Filter& aFilter,
                         gfxFloat aOpacity = 1.0,
                         const gfxMatrix& aTransform = gfxMatrix()) = 0;
+
     virtual bool DrawWithSamplingRect(gfxContext* aContext,
                                       const gfxRect& aFillRect,
                                       const gfxRect& aSamplingRect,
-                                      bool aRepeat,
+                                      mozilla::gfx::ExtendMode aExtendMode,
                                       const mozilla::gfx::Filter& aFilter,
                                       gfxFloat aOpacity = 1.0)
     {
         return false;
     }
 
     virtual mozilla::gfx::IntSize Size() { return mSize; }
 
@@ -64,32 +66,33 @@ protected:
 class gfxSurfaceDrawable : public gfxDrawable {
 public:
     gfxSurfaceDrawable(mozilla::gfx::SourceSurface* aSurface, const mozilla::gfx::IntSize aSize,
                        const gfxMatrix aTransform = gfxMatrix());
     virtual ~gfxSurfaceDrawable() {}
 
     virtual bool Draw(gfxContext* aContext,
                         const gfxRect& aFillRect,
-                        bool aRepeat,
+                        mozilla::gfx::ExtendMode aExtendMode,
                         const mozilla::gfx::Filter& aFilter,
                         gfxFloat aOpacity = 1.0,
                         const gfxMatrix& aTransform = gfxMatrix());
+
     virtual bool DrawWithSamplingRect(gfxContext* aContext,
                                       const gfxRect& aFillRect,
                                       const gfxRect& aSamplingRect,
-                                      bool aRepeat,
+                                      mozilla::gfx::ExtendMode aExtendMode,
                                       const mozilla::gfx::Filter& aFilter,
                                       gfxFloat aOpacity = 1.0);
-    
+
 protected:
     void DrawInternal(gfxContext* aContext,
                       const gfxRect& aFillRect,
                       const mozilla::gfx::IntRect& aSamplingRect,
-                      bool aRepeat,
+                      mozilla::gfx::ExtendMode aExtendMode,
                       const mozilla::gfx::Filter& aFilter,
                       gfxFloat aOpacity,
                       const gfxMatrix& aTransform = gfxMatrix());
 
     RefPtr<mozilla::gfx::SourceSurface> mSourceSurface;
     const gfxMatrix mTransform;
 };
 
@@ -124,17 +127,17 @@ public:
  */
 class gfxCallbackDrawable : public gfxDrawable {
 public:
     gfxCallbackDrawable(gfxDrawingCallback* aCallback, const mozilla::gfx::IntSize aSize);
     virtual ~gfxCallbackDrawable() {}
 
     virtual bool Draw(gfxContext* aContext,
                       const gfxRect& aFillRect,
-                      bool aRepeat,
+                      mozilla::gfx::ExtendMode aExtendMode,
                       const mozilla::gfx::Filter& aFilter,
                       gfxFloat aOpacity = 1.0,
                       const gfxMatrix& aTransform = gfxMatrix());
 
 protected:
     already_AddRefed<gfxSurfaceDrawable> MakeSurfaceDrawable(mozilla::gfx::Filter aFilter = mozilla::gfx::Filter::LINEAR);
 
     RefPtr<gfxDrawingCallback> mCallback;
@@ -148,20 +151,21 @@ protected:
 class gfxPatternDrawable : public gfxDrawable {
 public:
     gfxPatternDrawable(gfxPattern* aPattern,
                        const mozilla::gfx::IntSize aSize);
     virtual ~gfxPatternDrawable();
 
     virtual bool Draw(gfxContext* aContext,
                       const gfxRect& aFillRect,
-                      bool aRepeat,
+                      mozilla::gfx::ExtendMode aExtendMode,
                       const mozilla::gfx::Filter& aFilter,
                       gfxFloat aOpacity = 1.0,
                       const gfxMatrix& aTransform = gfxMatrix());
 
+
 protected:
     already_AddRefed<gfxCallbackDrawable> MakeCallbackDrawable();
 
     RefPtr<gfxPattern> mPattern;
 };
 
 #endif /* GFX_DRAWABLE_H */
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -454,17 +454,17 @@ CreateSamplingRestrictedDrawable(gfxDraw
     RefPtr<DrawTarget> target =
       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size, aFormat);
     if (!target) {
       return nullptr;
     }
 
     RefPtr<gfxContext> tmpCtx = new gfxContext(target);
     tmpCtx->SetOp(OptimalFillOp());
-    aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true, Filter::LINEAR,
+    aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT, Filter::LINEAR,
                     1.0, gfxMatrix::Translation(needed.TopLeft()));
     RefPtr<SourceSurface> surface = target->Snapshot();
 
     RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size, gfxMatrix::Translation(-needed.TopLeft()));
     return drawable.forget();
 }
 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
 
@@ -678,17 +678,17 @@ PrescaleAndTileDrawable(gfxDrawable* aDr
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(scaledImageSize, aFormat);
   if (!scaledDT) {
     return false;
   }
 
   RefPtr<gfxContext> tmpCtx = new gfxContext(scaledDT);
   scaledDT->SetTransform(ToMatrix(scaleMatrix));
   gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, aImageRect.height);
-  aDrawable->Draw(tmpCtx, gfxImageRect, true, aFilter, 1.0, gfxMatrix());
+  aDrawable->Draw(tmpCtx, gfxImageRect, ExtendMode::REPEAT, aFilter, 1.0, gfxMatrix());
 
   RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
 
   {
     gfxContextMatrixAutoSaveRestore autoSR(aContext);
     Matrix withoutScale = ToMatrix(aContext->CurrentMatrix());
     DrawTarget* destDrawTarget = aContext->GetDrawTarget();
 
@@ -717,19 +717,17 @@ gfxUtils::DrawPixelSnapped(gfxContext*  
                            uint32_t            aImageFlags,
                            gfxFloat            aOpacity)
 {
     PROFILER_LABEL("gfxUtils", "DrawPixelSnapped",
       js::ProfileEntry::Category::GRAPHICS);
 
     gfxRect imageRect(gfxPoint(0, 0), aImageSize);
     gfxRect region(aRegion.Rect());
-
-    bool doTile = !imageRect.Contains(region) &&
-                  !(aImageFlags & imgIContainer::FLAG_CLAMP);
+    ExtendMode extendMode = aRegion.GetExtendMode();
 
     RefPtr<gfxASurface> currentTarget = aContext->CurrentSurface();
     gfxMatrix deviceSpaceToImageSpace = DeviceToImageTransform(aContext);
 
     AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace,
                                             region, currentTarget);
     if (!workaround.Succeeded())
         return;
@@ -740,20 +738,21 @@ gfxUtils::DrawPixelSnapped(gfxContext*  
                                      imageRect.Width(), imageRect.Height(),
                                      region.Width(), region.Height());
 
     // OK now, the hard part left is to account for the subimage sampling
     // restriction. If all the transforms involved are just integer
     // translations, then we assume no resampling will occur so there's
     // nothing to do.
     // XXX if only we had source-clipping in cairo!
+
     if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
-        if (doTile || !aRegion.RestrictionContains(imageRect)) {
+        if ((extendMode != ExtendMode::CLAMP) || !aRegion.RestrictionContains(imageRect)) {
             if (drawable->DrawWithSamplingRect(aContext, aRegion.Rect(), aRegion.Restriction(),
-                                               doTile, aFilter, aOpacity)) {
+                                               extendMode, aFilter, aOpacity)) {
               return;
             }
 
 #ifdef MOZ_WIDGET_COCOA
             if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion,
                                         ToRect(imageRect), aFilter,
                                         aFormat, aOpacity)) {
               return;
@@ -768,23 +767,23 @@ gfxUtils::DrawPixelSnapped(gfxContext*  
               CreateSamplingRestrictedDrawable(aDrawable, aContext,
                                                aRegion, aFormat);
             if (restrictedDrawable) {
               drawable.swap(restrictedDrawable);
 
               // We no longer need to tile: Either we never needed to, or we already
               // filled a surface with the tiled pattern; this surface can now be
               // drawn without tiling.
-              doTile = false;
+              extendMode = ExtendMode::CLAMP;
             }
 #endif
         }
     }
 
-    drawable->Draw(aContext, aRegion.Rect(), doTile, aFilter, aOpacity);
+    drawable->Draw(aContext, aRegion.Rect(), extendMode, aFilter, aOpacity, gfxMatrix());
 }
 
 /* static */ int
 gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat)
 {
     switch (aFormat) {
         case gfxImageFormat::ARGB32:
             return 32;
--- a/image/ImageRegion.h
+++ b/image/ImageRegion.h
@@ -2,57 +2,64 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_ImageRegion_h
 #define mozilla_image_ImageRegion_h
 
 #include "gfxRect.h"
+#include "mozilla/gfx/Types.h"
 
 namespace mozilla {
 namespace image {
 
 /**
  * An axis-aligned rectangle in tiled image space, with an optional sampling
  * restriction rect. The drawing code ensures that if a sampling restriction
  * rect is present, any pixels sampled during the drawing process are found
  * within that rect.
  *
  * The sampling restriction rect exists primarily for callers which perform
  * pixel snapping. Other callers should generally use one of the Create()
  * overloads.
  */
 class ImageRegion
 {
+  typedef mozilla::gfx::ExtendMode ExtendMode;
+
 public:
   static ImageRegion Empty()
   {
-    return ImageRegion(gfxRect());
+    return ImageRegion(gfxRect(), ExtendMode::CLAMP);
   }
 
-  static ImageRegion Create(const gfxRect& aRect)
+  static ImageRegion Create(const gfxRect& aRect,
+                            ExtendMode aExtendMode = ExtendMode::CLAMP)
   {
-    return ImageRegion(aRect);
+    return ImageRegion(aRect, aExtendMode);
   }
 
-  static ImageRegion Create(const gfxSize& aSize)
+  static ImageRegion Create(const gfxSize& aSize,
+                            ExtendMode aExtendMode = ExtendMode::CLAMP)
   {
-    return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height));
+    return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode);
   }
 
-  static ImageRegion Create(const nsIntSize& aSize)
+  static ImageRegion Create(const nsIntSize& aSize,
+                            ExtendMode aExtendMode = ExtendMode::CLAMP)
   {
-    return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height));
+    return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode);
   }
 
   static ImageRegion CreateWithSamplingRestriction(const gfxRect& aRect,
-                                                   const gfxRect& aRestriction)
+                                                   const gfxRect& aRestriction,
+                                                   ExtendMode aExtendMode = ExtendMode::CLAMP)
   {
-    return ImageRegion(aRect, aRestriction);
+    return ImageRegion(aRect, aRestriction, aExtendMode);
   }
 
   bool IsRestricted() const { return mIsRestricted; }
   const gfxRect& Rect() const { return mRect; }
 
   const gfxRect& Restriction() const
   {
     MOZ_ASSERT(mIsRestricted);
@@ -128,31 +135,39 @@ public:
   ImageRegion operator+(const gfxPoint& aPt) const
   {
     if (mIsRestricted) {
       return CreateWithSamplingRestriction(mRect + aPt, mRestriction + aPt);
     }
     return Create(mRect + aPt);
   }
 
+  gfx::ExtendMode GetExtendMode() const
+  {
+    return mExtendMode;
+  }
+
   /* ImageRegion() : mIsRestricted(false) { } */
 
 private:
-  explicit ImageRegion(const gfxRect& aRect)
+  explicit ImageRegion(const gfxRect& aRect, ExtendMode aExtendMode)
     : mRect(aRect)
+    , mExtendMode(aExtendMode)
     , mIsRestricted(false)
   { }
 
-  ImageRegion(const gfxRect& aRect, const gfxRect& aRestriction)
+  ImageRegion(const gfxRect& aRect, const gfxRect& aRestriction, ExtendMode aExtendMode)
     : mRect(aRect)
     , mRestriction(aRestriction)
+    , mExtendMode(aExtendMode)
     , mIsRestricted(true)
   { }
 
   gfxRect mRect;
   gfxRect mRestriction;
+  ExtendMode mExtendMode;
   bool    mIsRestricted;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_ImageRegion_h
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -517,17 +517,17 @@ imgFrame::SurfaceForDrawing(bool        
 
     // Fill 'available' with whatever we've got
     if (mSinglePixel) {
       target->FillRect(ToRect(aRegion.Intersect(available).Rect()),
                        ColorPattern(mSinglePixelColor),
                        DrawOptions(1.0f, CompositionOp::OP_SOURCE));
     } else {
       SurfacePattern pattern(aSurface,
-                             ExtendMode::REPEAT,
+                             aRegion.GetExtendMode(),
                              Matrix::Translation(mDecoded.x, mDecoded.y));
       target->FillRect(ToRect(aRegion.Intersect(available).Rect()), pattern);
     }
 
     RefPtr<SourceSurface> newsurf = target->Snapshot();
     return SurfaceWithFormat(new gfxSurfaceDrawable(newsurf, size),
                              target->GetFormat());
   }
@@ -581,16 +581,17 @@ bool imgFrame::Draw(gfxContext* aContext
   RefPtr<SourceSurface> surf = GetSurfaceInternal();
   if (!surf && !mSinglePixel) {
     return false;
   }
 
   gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height);
   bool doTile = !imageRect.Contains(aRegion.Rect()) &&
                 !(aImageFlags & imgIContainer::FLAG_CLAMP);
+
   ImageRegion region(aRegion);
   // SurfaceForDrawing changes the current transform, and we need it to still
   // be changed when we call gfxUtils::DrawPixelSnapped. We still need to
   // restore it before returning though.
   // XXXjwatt In general having functions require someone further up the stack
   // to undo transform changes that they make is bad practice. We should
   // change how this code works.
   gfxContextMatrixAutoSaveRestore autoSR(aContext);
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -3330,24 +3330,40 @@ nsCSSRendering::PrepareBackgroundLayer(n
                                             &imageTopLeft, &state.mAnchor);
   imageTopLeft += bgPositioningArea.TopLeft();
   state.mAnchor += bgPositioningArea.TopLeft();
 
   state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
   state.mFillArea = state.mDestArea;
   int repeatX = aLayer.mRepeat.mXRepeat;
   int repeatY = aLayer.mRepeat.mYRepeat;
+
+  ExtendMode repeatMode = ExtendMode::CLAMP;
   if (repeatX == NS_STYLE_BG_REPEAT_REPEAT) {
     state.mFillArea.x = bgClipRect.x;
     state.mFillArea.width = bgClipRect.width;
+    repeatMode = ExtendMode::REPEAT_X;
   }
   if (repeatY == NS_STYLE_BG_REPEAT_REPEAT) {
     state.mFillArea.y = bgClipRect.y;
     state.mFillArea.height = bgClipRect.height;
-  }
+
+    /***
+     * We're repeating on the X axis already,
+     * so if we have to repeat in the Y axis,
+     * we really need to repeat in both directions.
+     */
+    if (repeatMode == ExtendMode::REPEAT_X) {
+      repeatMode = ExtendMode::REPEAT;
+    } else {
+      repeatMode = ExtendMode::REPEAT_Y;
+    }
+  }
+  state.mImageRenderer.SetExtendMode(repeatMode);
+
   state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
 
   state.mCompositionOp = GetGFXBlendMode(aLayer.mBlendMode);
 
   return state;
 }
 
 nsRect
@@ -4643,16 +4659,17 @@ nsImageRenderer::nsImageRenderer(nsIFram
   , mImage(aImage)
   , mType(aImage->GetType())
   , mImageContainer(nullptr)
   , mGradientData(nullptr)
   , mPaintServerFrame(nullptr)
   , mPrepareResult(DrawResult::NOT_READY)
   , mSize(0, 0)
   , mFlags(aFlags)
+  , mExtendMode(ExtendMode::CLAMP)
 {
 }
 
 nsImageRenderer::~nsImageRenderer()
 {
 }
 
 static bool
@@ -5031,17 +5048,18 @@ nsImageRenderer::Draw(nsPresContext*    
     {
       CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
                            nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
       return
         nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
                                            aPresContext,
                                            mImageContainer, imageSize, filter,
                                            aDest, aFill, aAnchor, aDirtyRect,
-                                           ConvertImageRendererToDrawFlags(mFlags));
+                                           ConvertImageRendererToDrawFlags(mFlags),
+                                           mExtendMode);
     }
     case eStyleImageType_Gradient:
     {
       nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
                                     mGradientData, aDirtyRect,
                                     aDest, aFill, aSrc, mSize);
       return DrawResult::SUCCESS;
     }
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -256,16 +256,17 @@ public:
   bool IsContainerAvailable(LayerManager* aManager,
                             nsDisplayListBuilder* aBuilder);
 
   /// Retrieves the image associated with this nsImageRenderer, if there is one.
   already_AddRefed<imgIContainer> GetImage();
 
   bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
   DrawResult PrepareResult() const { return mPrepareResult; }
+  void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
 
 private:
   /**
    * Draws the image to the target rendering context.
    * aSrc is a rect on the source image which will be mapped to aDest; it's
    * currently only used for gradients.
    *
    * @see nsLayoutUtils::DrawImage() for other parameters.
@@ -292,16 +293,17 @@ private:
   nsStyleImageType          mType;
   nsCOMPtr<imgIContainer>   mImageContainer;
   RefPtr<nsStyleGradient> mGradientData;
   nsIFrame*                 mPaintServerFrame;
   nsLayoutUtils::SurfaceFromElementResult mImageElementSurface;
   DrawResult                mPrepareResult;
   nsSize                    mSize; // unscaled size of the image, in app units
   uint32_t                  mFlags;
+  mozilla::gfx::ExtendMode  mExtendMode;
 };
 
 /**
  * A struct representing all the information needed to paint a background
  * image to some target, taking into account all CSS background-* properties.
  * See PrepareBackgroundLayer.
  */
 struct nsBackgroundLayerState {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -6214,17 +6214,18 @@ static SnappedImageDrawingParameters
 ComputeSnappedImageDrawingParameters(gfxContext*     aCtx,
                                      int32_t         aAppUnitsPerDevPixel,
                                      const nsRect    aDest,
                                      const nsRect    aFill,
                                      const nsPoint   aAnchor,
                                      const nsRect    aDirty,
                                      imgIContainer*  aImage,
                                      Filter          aGraphicsFilter,
-                                     uint32_t        aImageFlags)
+                                     uint32_t        aImageFlags,
+                                     ExtendMode      aExtendMode)
 {
   if (aDest.IsEmpty() || aFill.IsEmpty())
     return SnappedImageDrawingParameters();
 
   // Avoid unnecessarily large offsets.
   bool doTile = !aDest.Contains(aFill);
   nsRect appUnitDest = doTile ? TileNearRect(aDest, aFill.Intersect(aDirty))
                               : aDest;
@@ -6383,35 +6384,45 @@ ComputeSnappedImageDrawingParameters(gfx
 
   // If we didn't snap, we need to post-multiply the matrix on the context to
   // get the final matrix we'll draw with, because we didn't take it into
   // account when computing the matrices above.
   if (!didSnap) {
     transform = transform * currentMatrix;
   }
 
+  ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP)
+                          ? ExtendMode::CLAMP
+                          : aExtendMode;
+  // We were passed in the default extend mode but need to tile.
+  if (extendMode == ExtendMode::CLAMP && doTile) {
+    MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP));
+    extendMode = ExtendMode::REPEAT;
+  }
+
   ImageRegion region =
-    ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage);
+    ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage, extendMode);
 
   return SnappedImageDrawingParameters(transform, intImageSize,
                                        region, svgViewportSize);
 }
 
 
 static DrawResult
 DrawImageInternal(gfxContext&            aContext,
                   nsPresContext*         aPresContext,
                   imgIContainer*         aImage,
                   Filter                 aGraphicsFilter,
                   const nsRect&          aDest,
                   const nsRect&          aFill,
                   const nsPoint&         aAnchor,
                   const nsRect&          aDirty,
                   const SVGImageContext* aSVGContext,
-                  uint32_t               aImageFlags)
+                  uint32_t               aImageFlags,
+                  ExtendMode             aExtendMode = ExtendMode::CLAMP)
 {
   DrawResult result = DrawResult::SUCCESS;
 
   if (aPresContext->Type() == nsPresContext::eContext_Print) {
     // We want vector images to be passed on as vector commands, not a raster
     // image.
     aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE;
   }
@@ -6419,17 +6430,17 @@ DrawImageInternal(gfxContext&           
     aImageFlags |= imgIContainer::FLAG_CLAMP;
   }
   int32_t appUnitsPerDevPixel =
    aPresContext->AppUnitsPerDevPixel();
 
   SnappedImageDrawingParameters params =
     ComputeSnappedImageDrawingParameters(&aContext, appUnitsPerDevPixel, aDest,
                                          aFill, aAnchor, aDirty, aImage,
-                                         aGraphicsFilter, aImageFlags);
+                                         aGraphicsFilter, aImageFlags, aExtendMode);
 
   if (!params.shouldDraw) {
     return result;
   }
 
   {
     gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
 
@@ -6629,30 +6640,31 @@ nsLayoutUtils::DrawBackgroundImage(gfxCo
                                    nsPresContext*      aPresContext,
                                    imgIContainer*      aImage,
                                    const CSSIntSize&   aImageSize,
                                    Filter              aGraphicsFilter,
                                    const nsRect&       aDest,
                                    const nsRect&       aFill,
                                    const nsPoint&      aAnchor,
                                    const nsRect&       aDirty,
-                                   uint32_t            aImageFlags)
+                                   uint32_t            aImageFlags,
+                                   ExtendMode          aExtendMode)
 {
   PROFILER_LABEL("layout", "nsLayoutUtils::DrawBackgroundImage",
                  js::ProfileEntry::Category::GRAPHICS);
 
   if (UseBackgroundNearestFiltering()) {
     aGraphicsFilter = Filter::POINT;
   }
 
   SVGImageContext svgContext(aImageSize, Nothing());
 
   return DrawImageInternal(aContext, aPresContext, aImage,
                            aGraphicsFilter, aDest, aFill, aAnchor,
-                           aDirty, &svgContext, aImageFlags);
+                           aDirty, &svgContext, aImageFlags, aExtendMode);
 }
 
 /* static */ DrawResult
 nsLayoutUtils::DrawImage(gfxContext&         aContext,
                          nsPresContext*      aPresContext,
                          imgIContainer*      aImage,
                          Filter              aGraphicsFilter,
                          const nsRect&       aDest,
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -114,16 +114,17 @@ class nsLayoutUtils
 {
   typedef mozilla::dom::DOMRectList DOMRectList;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
   typedef mozilla::IntrinsicSize IntrinsicSize;
   typedef mozilla::gfx::SourceSurface SourceSurface;
   typedef mozilla::gfx::Color Color;
   typedef mozilla::gfx::DrawTarget DrawTarget;
+  typedef mozilla::gfx::ExtendMode ExtendMode;
   typedef mozilla::gfx::Filter Filter;
   typedef mozilla::gfx::Float Float;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::Rect Rect;
   typedef mozilla::gfx::RectDouble RectDouble;
   typedef mozilla::gfx::Matrix4x4 Matrix4x4;
   typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
   typedef mozilla::gfx::StrokeOptions StrokeOptions;
@@ -1764,28 +1765,30 @@ public:
    *                            the image is a vector image being rendered at
    *                            that size.)
    *   @param aDest             The position and scaled area where one copy of
    *                            the image should be drawn.
    *   @param aFill             The area to be filled with copies of the image.
    *   @param aAnchor           A point in aFill which we will ensure is
    *                            pixel-aligned in the output.
    *   @param aDirty            Pixels outside this area may be skipped.
-   *   @param aImageFlags       Image flags of the imgIContainer::FLAG_* variety
+   *   @param aImageFlags       Image flags of the imgIContainer::FLAG_* variety.
+   *   @param aExtendMode       How to extend the image over the dest rect.
    */
   static DrawResult DrawBackgroundImage(gfxContext&         aContext,
                                         nsPresContext*      aPresContext,
                                         imgIContainer*      aImage,
                                         const CSSIntSize&   aImageSize,
                                         Filter              aGraphicsFilter,
                                         const nsRect&       aDest,
                                         const nsRect&       aFill,
                                         const nsPoint&      aAnchor,
                                         const nsRect&       aDirty,
-                                        uint32_t            aImageFlags);
+                                        uint32_t            aImageFlags,
+                                        ExtendMode          aExtendMode);
 
   /**
    * Draw an image.
    * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
    *   @param aRenderingContext Where to draw the image, set up with an
    *                            appropriate scale and transform for drawing in
    *                            app units.
    *   @param aImage            The image.