Bug 1355570 - Simplify nsCSSRenderingGradients r?mattwoodrow draft
authorRyan Hunt <rhunt@eqrion.net>
Tue, 11 Apr 2017 14:30:13 -0400
changeset 560653 8e94071eadf839ec765fb1d52c5bb4db83fa9d42
parent 560652 0fa17b46b0d1e7cf7342bdc78e52171d9a69d41e
child 560654 b99fec9dd9042618c66da3e9b26b70a5f2c8ebcf
push id53497
push userbmo:rhunt@eqrion.net
push dateTue, 11 Apr 2017 18:52:32 +0000
reviewersmattwoodrow
bugs1355570, 1341101
milestone55.0a1
Bug 1355570 - Simplify nsCSSRenderingGradients r?mattwoodrow In nsCSSRenderingGradients the logic for handling many error conditions and normalizing the gradient stops is shared between the paint path and the WebRender path. WebRender now normalizes gradients and handles any error conditions that it needs to. There are some conditions that are not problems for it, like repeating radial gradients with stops below zero. This commit undoes the work done in bug 1341101 to share this logic. Some conditions were moved around in bug 1341101 to make things simpler, and that has been undone. Now the paint path is identical to how it was originally. There is one exception, which is ResolveMidpoints which is kept between both code paths. This should be safe. MozReview-Commit-ID: LMhMNXNquXM
layout/painting/nsCSSRenderingGradients.cpp
layout/painting/nsCSSRenderingGradients.h
layout/painting/nsDisplayList.cpp
layout/painting/nsImageRenderer.cpp
--- a/layout/painting/nsCSSRenderingGradients.cpp
+++ b/layout/painting/nsCSSRenderingGradients.cpp
@@ -533,35 +533,25 @@ ClampColorStops(nsTArray<ColorStop>& aSt
   }
   if (aStops.LastElement().mPosition < 1) {
     aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
   }
 }
 
 namespace mozilla {
 
-Maybe<nsCSSGradientRenderer>
+nsCSSGradientRenderer
 nsCSSGradientRenderer::Create(nsPresContext* aPresContext,
                              nsStyleGradient* aGradient,
-                             const nsRect& aDest,
-                             const nsRect& aFillArea,
-                             const nsSize& aRepeatSize,
-                             const CSSIntRect& aSrc,
                              const nsSize& aIntrinsicSize)
 {
-  if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
-    return Nothing();
-  }
-
   nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
   gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
                             gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
 
-  bool cellContainsFill = aDest.Contains(aFillArea);
-
   // Compute "gradient line" start and end relative to the intrinsic size of
   // the gradient.
   gfxPoint lineStart, lineEnd;
   double radiusX = 0, radiusY = 0; // for radial gradients only
   if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
     ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
                               &lineStart, &lineEnd);
   } else {
@@ -643,220 +633,203 @@ nsCSSGradientRenderer::Create(nsPresCont
       for (uint32_t j = firstUnsetPosition; j < i; ++j) {
         p += d;
         stops[j].mPosition = p;
       }
       firstUnsetPosition = -1;
     }
   }
 
+  ResolveMidpoints(stops);
+
+  nsCSSGradientRenderer renderer;
+  renderer.mPresContext = aPresContext;
+  renderer.mGradient = aGradient;
+  renderer.mStops = std::move(stops);
+  renderer.mLineStart = lineStart;
+  renderer.mLineEnd = lineEnd;
+  renderer.mRadiusX = radiusX;
+  renderer.mRadiusY = radiusY;
+  return renderer;
+}
+
+void
+nsCSSGradientRenderer::Paint(gfxContext& aContext,
+                             const nsRect& aDest,
+                             const nsRect& aFillArea,
+                             const nsSize& aRepeatSize,
+                             const CSSIntRect& aSrc,
+                             const nsRect& aDirtyRect,
+                             float aOpacity)
+{
+  PROFILER_LABEL("nsCSSRendering", "PaintGradient",
+    js::ProfileEntry::Category::GRAPHICS);
+  Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
+
+  if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
+    return;
+  }
+
+  nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
+
+  gfxFloat lineLength = NS_hypot(mLineEnd.x - mLineStart.x,
+                                 mLineEnd.y - mLineStart.y);
+  bool cellContainsFill = aDest.Contains(aFillArea);
+
   // If a non-repeating linear gradient is axis-aligned and there are no gaps
   // between tiles, we can optimise away most of the work by converting to a
   // repeating linear gradient and filling the whole destination rect at once.
   bool forceRepeatToCoverTiles =
-    aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
-    (lineStart.x == lineEnd.x) != (lineStart.y == lineEnd.y) &&
+    mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
+    (mLineStart.x == mLineEnd.x) != (mLineStart.y == mLineEnd.y) &&
     aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
-    !aGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
-  bool forceRepeatToCoverTilesFlip = false;
+    !mGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
 
+  gfxMatrix matrix;
   if (forceRepeatToCoverTiles) {
     // Length of the source rectangle along the gradient axis.
     double rectLen;
     // The position of the start of the rectangle along the gradient.
     double offset;
 
     // The gradient line is "backwards". Flip the line upside down to make
     // things easier, and then rotate the matrix to turn everything back the
     // right way up.
-    if (lineStart.x > lineEnd.x || lineStart.y > lineEnd.y) {
-      std::swap(lineStart, lineEnd);
-      forceRepeatToCoverTilesFlip = true;
+    if (mLineStart.x > mLineEnd.x || mLineStart.y > mLineEnd.y) {
+      std::swap(mLineStart, mLineEnd);
+      matrix.Scale(-1, -1);
     }
 
     // Fit the gradient line exactly into the source rect.
     // aSrc is relative to aIntrinsincSize.
     // srcRectDev will be relative to srcSize, so in the same coordinate space
     // as lineStart / lineEnd.
     gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
       CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
-    if (lineStart.x != lineEnd.x) {
+    if (mLineStart.x != mLineEnd.x) {
       rectLen = srcRectDev.width;
-      offset = (srcRectDev.x - lineStart.x) / lineLength;
-      lineStart.x = srcRectDev.x;
-      lineEnd.x = srcRectDev.XMost();
+      offset = (srcRectDev.x - mLineStart.x) / lineLength;
+      mLineStart.x = srcRectDev.x;
+      mLineEnd.x = srcRectDev.XMost();
     } else {
       rectLen = srcRectDev.height;
-      offset = (srcRectDev.y - lineStart.y) / lineLength;
-      lineStart.y = srcRectDev.y;
-      lineEnd.y = srcRectDev.YMost();
+      offset = (srcRectDev.y - mLineStart.y) / lineLength;
+      mLineStart.y = srcRectDev.y;
+      mLineEnd.y = srcRectDev.YMost();
     }
 
     // Adjust gradient stop positions for the new gradient line.
     double scale = lineLength / rectLen;
-    for (size_t i = 0; i < stops.Length(); i++) {
-      stops[i].mPosition = (stops[i].mPosition - offset) * fabs(scale);
+    for (size_t i = 0; i < mStops.Length(); i++) {
+      mStops[i].mPosition = (mStops[i].mPosition - offset) * fabs(scale);
     }
 
     // Clamp or extrapolate gradient stops to exactly [0, 1].
-    ClampColorStops(stops);
+    ClampColorStops(mStops);
 
     lineLength = rectLen;
   }
 
   // Eliminate negative-position stops if the gradient is radial.
-  double firstStop = stops[0].mPosition;
-  if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
-    if (aGradient->mRepeating) {
+  double firstStop = mStops[0].mPosition;
+  if (mGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
+    if (mGradient->mRepeating) {
       // Choose an instance of the repeated pattern that gives us all positive
       // stop-offsets.
-      double lastStop = stops[stops.Length() - 1].mPosition;
+      double lastStop = mStops[mStops.Length() - 1].mPosition;
       double stopDelta = lastStop - firstStop;
       // If all the stops are in approximately the same place then logic below
       // will kick in that makes us draw just the last stop color, so don't
       // try to do anything in that case. We certainly need to avoid
       // dividing by zero.
       if (stopDelta >= 1e-6) {
         double instanceCount = ceil(-firstStop/stopDelta);
         // Advance stops by instanceCount multiples of the period of the
         // repeating gradient.
         double offset = instanceCount*stopDelta;
-        for (uint32_t i = 0; i < stops.Length(); i++) {
-          stops[i].mPosition += offset;
+        for (uint32_t i = 0; i < mStops.Length(); i++) {
+          mStops[i].mPosition += offset;
         }
       }
     } else {
       // Move negative-position stops to position 0.0. We may also need
       // to set the color of the stop to the color the gradient should have
       // at the center of the ellipse.
-      for (uint32_t i = 0; i < stops.Length(); i++) {
-        double pos = stops[i].mPosition;
+      for (uint32_t i = 0; i < mStops.Length(); i++) {
+        double pos = mStops[i].mPosition;
         if (pos < 0.0) {
-          stops[i].mPosition = 0.0;
+          mStops[i].mPosition = 0.0;
           // If this is the last stop, we don't need to adjust the color,
           // it will fill the entire area.
-          if (i < stops.Length() - 1) {
-            double nextPos = stops[i + 1].mPosition;
+          if (i < mStops.Length() - 1) {
+            double nextPos = mStops[i + 1].mPosition;
             // If nextPos is approximately equal to pos, then we don't
             // need to adjust the color of this stop because it's
             // not going to be displayed.
             // If nextPos is negative, we don't need to adjust the color of
             // this stop since it's not going to be displayed because
             // nextPos will also be moved to 0.0.
             if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
               // Compute how far the new position 0.0 is along the interval
               // between pos and nextPos.
               // XXX Color interpolation (in cairo, too) should use the
               // CSS 'color-interpolation' property!
               float frac = float((0.0 - pos)/(nextPos - pos));
-              stops[i].mColor =
-                InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
+              mStops[i].mColor =
+                InterpolateColor(mStops[i].mColor, mStops[i + 1].mColor, frac);
             }
           }
         }
       }
     }
-    firstStop = stops[0].mPosition;
+    firstStop = mStops[0].mPosition;
     MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
   }
 
-  double lastStop = stops[stops.Length() - 1].mPosition;
-  double stopDelta = lastStop - firstStop;
-  bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
-                      (radiusX < 1e-6 || radiusY < 1e-6);
-  if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
-    // Stops are all at the same place. Map all stops to 0.0.
-    // For repeating radial gradients, or for any radial gradients with
-    // a zero radius, we need to fill with the last stop color, so just set
-    // both radii to 0.
-    if (aGradient->mRepeating || zeroRadius) {
-      radiusX = radiusY = 0.0;
-    }
-
-    // Non-repeating gradient with all stops in same place -> just add
-    // first stop and last stop, both at position 0.
-    // Repeating gradient with all stops in the same place, or radial
-    // gradient with radius of 0 -> just paint the last stop color.
-    // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
-    Color firstColor(stops[0].mColor);
-    Color lastColor(stops.LastElement().mColor);
-    stops.Clear();
-
-    if (!aGradient->mRepeating && !zeroRadius) {
-      stops.AppendElement(ColorStop(firstStop, false, firstColor));
-    }
-    stops.AppendElement(ColorStop(firstStop, false, lastColor));
-  }
-
-  ResolveMidpoints(stops);
-
-  nsCSSGradientRenderer renderer;
-  renderer.mPresContext = aPresContext;
-  renderer.mGradient = aGradient;
-  renderer.mSrc = aSrc;
-  renderer.mDest = aDest;
-  renderer.mFillArea = aFillArea;
-  renderer.mRepeatSize = aRepeatSize;
-  renderer.mStops = std::move(stops);
-  renderer.mLineStart = lineStart;
-  renderer.mLineEnd = lineEnd;
-  renderer.mRadiusX = radiusX;
-  renderer.mRadiusY = radiusY;
-  renderer.mForceRepeatToCoverTiles = forceRepeatToCoverTiles;
-  renderer.mForceRepeatToCoverTilesFlip = forceRepeatToCoverTilesFlip;
-  return Some(renderer);
-}
-
-void
-nsCSSGradientRenderer::Paint(gfxContext& aContext,
-                             const nsRect& aDirtyRect,
-                             float aOpacity)
-{
-  PROFILER_LABEL("nsCSSRendering", "PaintGradient",
-    js::ProfileEntry::Category::GRAPHICS);
-  Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
-
-  nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
-
-  double firstStop = mStops[0].mPosition;
-  double lastStop = mStops[mStops.Length() - 1].mPosition;
-
   if (mGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !mGradient->mRepeating) {
     // Direct2D can only handle a particular class of radial gradients because
     // of the way the it specifies gradients. Setting firstStop to 0, when we
     // can, will help us stay on the fast path. Currently we don't do this
     // for repeating gradients but we could by adjusting the stop collection
     // to start at 0
     firstStop = 0;
   }
 
+  double lastStop = mStops[mStops.Length() - 1].mPosition;
   // Cairo gradients must have stop positions in the range [0, 1]. So,
   // stop positions will be normalized below by subtracting firstStop and then
   // multiplying by stopScale.
   double stopScale;
   double stopOrigin = firstStop;
   double stopEnd = lastStop;
   double stopDelta = lastStop - firstStop;
+  bool zeroRadius = mGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
+                      (mRadiusX < 1e-6 || mRadiusY < 1e-6);
+  if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
+    // Stops are all at the same place. Map all stops to 0.0.
+    // For repeating radial gradients, or for any radial gradients with
+    // a zero radius, we need to fill with the last stop color, so just set
+    // both radii to 0.
+    if (mGradient->mRepeating || zeroRadius) {
+      mRadiusX = mRadiusY = 0.0;
+    }
+    stopDelta = 0.0;
+    lastStop = firstStop;
+  }
 
   // Don't normalize non-repeating or degenerate gradients below 0..1
   // This keeps the gradient line as large as the box and doesn't
   // lets us avoiding having to get padding correct for stops
   // at 0 and 1
   if (!mGradient->mRepeating || stopDelta == 0.0) {
     stopOrigin = std::min(stopOrigin, 0.0);
     stopEnd = std::max(stopEnd, 1.0);
   }
   stopScale = 1.0/(stopEnd - stopOrigin);
 
-  // Create the transform
-  gfxMatrix matrix;
-  if (mForceRepeatToCoverTilesFlip) {
-    matrix.Scale(-1, -1);
-  }
-
   // Create the gradient pattern.
   RefPtr<gfxPattern> gradientPattern;
   gfxPoint gradientStart;
   gfxPoint gradientEnd;
   if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
     // Compute the actual gradient line ends we need to pass to cairo after
     // stops have been normalized.
     gradientStart = mLineStart + (mLineEnd - mLineStart)*stopOrigin;
@@ -896,25 +869,41 @@ nsCSSGradientRenderer::Paint(gfxContext&
       // So to stretch the ellipse by factor of P vertically, we scale
       // user coordinates by 1/P.
       matrix.Translate(mLineStart);
       matrix.Scale(1.0, mRadiusX/mRadiusY);
       matrix.Translate(-mLineStart);
     }
   }
   // Use a pattern transform to take account of source and dest rects
-  matrix.Translate(gfxPoint(mPresContext->CSSPixelsToDevPixels(mSrc.x),
-                            mPresContext->CSSPixelsToDevPixels(mSrc.y)));
-  matrix.Scale(gfxFloat(mPresContext->CSSPixelsToAppUnits(mSrc.width))/mDest.width,
-               gfxFloat(mPresContext->CSSPixelsToAppUnits(mSrc.height))/mDest.height);
+  matrix.Translate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x),
+                            mPresContext->CSSPixelsToDevPixels(aSrc.y)));
+  matrix.Scale(gfxFloat(mPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
+               gfxFloat(mPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
   gradientPattern->SetMatrix(matrix);
 
+  if (stopDelta == 0.0) {
+    // Non-repeating gradient with all stops in same place -> just add
+    // first stop and last stop, both at position 0.
+    // Repeating gradient with all stops in the same place, or radial
+    // gradient with radius of 0 -> just paint the last stop color.
+    // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
+    Color firstColor(mStops[0].mColor);
+    Color lastColor(mStops.LastElement().mColor);
+    mStops.Clear();
+
+    if (!mGradient->mRepeating && !zeroRadius) {
+      mStops.AppendElement(ColorStop(firstStop, false, firstColor));
+    }
+    mStops.AppendElement(ColorStop(firstStop, false, lastColor));
+  }
+
   ResolvePremultipliedAlpha(mStops);
 
-  bool isRepeat = mGradient->mRepeating || mForceRepeatToCoverTiles;
+  bool isRepeat = mGradient->mRepeating || forceRepeatToCoverTiles;
 
   // Now set normalized color stops in pattern.
   // Offscreen gradient surface cache (not a tile):
   // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
   // which is a lookup table used to evaluate the gradient. This surface can use
   // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
   // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
   // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
@@ -931,44 +920,44 @@ nsCSSGradientRenderer::Paint(gfxContext&
                                                isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
   gradientPattern->SetColorStops(gs);
 
   // Paint gradient tiles. This isn't terribly efficient, but doing it this
   // way is simple and sure to get pixel-snapping right. We could speed things
   // up by drawing tiles into temporary surfaces and copying those to the
   // destination, but after pixel-snapping tiles may not all be the same size.
   nsRect dirty;
-  if (!dirty.IntersectRect(aDirtyRect, mFillArea))
+  if (!dirty.IntersectRect(aDirtyRect, aFillArea))
     return;
 
   gfxRect areaToFill =
-    nsLayoutUtils::RectToGfxRect(mFillArea, appUnitsPerDevPixel);
+    nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
   gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
   dirtyAreaToFill.RoundOut();
 
   gfxMatrix ctm = aContext.CurrentMatrix();
   bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
 
   // xStart/yStart are the top-left corner of the top-left tile.
-  nscoord xStart = FindTileStart(dirty.x, mDest.x, mRepeatSize.width);
-  nscoord yStart = FindTileStart(dirty.y, mDest.y, mRepeatSize.height);
-  nscoord xEnd = mForceRepeatToCoverTiles ? xStart + mDest.width : dirty.XMost();
-  nscoord yEnd = mForceRepeatToCoverTiles ? yStart + mDest.height : dirty.YMost();
+  nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
+  nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
+  nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
+  nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
 
   // x and y are the top-left corner of the tile to draw
-  for (nscoord y = yStart; y < yEnd; y += mRepeatSize.height) {
-    for (nscoord x = xStart; x < xEnd; x += mRepeatSize.width) {
+  for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
+    for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
       // The coordinates of the tile
       gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
-                      nsRect(x, y, mDest.width, mDest.height),
+                      nsRect(x, y, aDest.width, aDest.height),
                       appUnitsPerDevPixel);
       // The actual area to fill with this tile is the intersection of this
       // tile with the overall area we're supposed to be filling
       gfxRect fillRect =
-        mForceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
+        forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
       // Try snapping the fill rect. Snap its top-left and bottom-right
       // independently to preserve the orientation.
       gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
       gfxPoint snappedFillRectTopRight = fillRect.TopRight();
       gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
       // Snap three points instead of just two to ensure we choose the
       // correct orientation if there's a reflection.
       if (isCTMPreservingAxisAlignedRectangles &&
@@ -1014,18 +1003,17 @@ nsCSSGradientRenderer::Paint(gfxContext&
 void
 nsCSSGradientRenderer::BuildWebRenderParameters(float aOpacity,
                                                 WrGradientExtendMode& aMode,
                                                 nsTArray<WrGradientStop>& aStops,
                                                 LayoutDevicePoint& aLineStart,
                                                 LayoutDevicePoint& aLineEnd,
                                                 LayoutDeviceSize& aGradientRadius)
 {
-  bool isRepeat = mGradient->mRepeating || mForceRepeatToCoverTiles;
-  aMode = isRepeat ? WrGradientExtendMode::Repeat : WrGradientExtendMode::Clamp;
+  aMode = mGradient->mRepeating ? WrGradientExtendMode::Repeat : WrGradientExtendMode::Clamp;
 
   aStops.SetLength(mStops.Length());
   for(uint32_t i = 0; i < mStops.Length(); i++) {
     float alpha = mStops[i].mColor.a * aOpacity;
     aStops[i].color.r = mStops[i].mColor.r * alpha;
     aStops[i].color.g = mStops[i].mColor.g * alpha;
     aStops[i].color.b = mStops[i].mColor.b * alpha;
     aStops[i].color.a = alpha;
@@ -1035,31 +1023,39 @@ nsCSSGradientRenderer::BuildWebRenderPar
   aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y);
   aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y);
   aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY);
 }
 
 void
 nsCSSGradientRenderer::BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder,
                                                   layers::WebRenderDisplayItemLayer* aLayer,
+                                                  const nsRect& aDest,
+                                                  const nsRect& aFillArea,
+                                                  const nsSize& aRepeatSize,
+                                                  const CSSIntRect& aSrc,
                                                   float aOpacity)
 {
+  if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
+    return;
+  }
+
   WrGradientExtendMode extendMode;
   nsTArray<WrGradientStop> stops;
   LayoutDevicePoint lineStart;
   LayoutDevicePoint lineEnd;
   LayoutDeviceSize gradientRadius;
   BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd, gradientRadius);
 
   nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel();
 
   // Translate the parameters into device coordinates
-  LayoutDeviceRect clipBounds = LayoutDevicePixel::FromAppUnits(mFillArea, appUnitsPerDevPixel);
-  LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits(mDest, appUnitsPerDevPixel);
-  LayoutDeviceSize tileRepeat = LayoutDevicePixel::FromAppUnits(mRepeatSize, appUnitsPerDevPixel);
+  LayoutDeviceRect clipBounds = LayoutDevicePixel::FromAppUnits(aFillArea, appUnitsPerDevPixel);
+  LayoutDeviceRect firstTileBounds = LayoutDevicePixel::FromAppUnits(aDest, appUnitsPerDevPixel);
+  LayoutDeviceSize tileRepeat = LayoutDevicePixel::FromAppUnits(aRepeatSize, appUnitsPerDevPixel);
 
   // Calculate the bounds of the gradient display item, which starts at the first
   // tile and extends to the end of clip bounds
   LayoutDevicePoint tileToClip = clipBounds.BottomRight() - firstTileBounds.TopLeft();
   LayoutDeviceRect gradientBounds = LayoutDeviceRect(firstTileBounds.TopLeft(),
                                                      LayoutDeviceSize(tileToClip.x, tileToClip.y));
 
   // Calculate the tile spacing, which is the repeat size minus the tile size
--- a/layout/painting/nsCSSRenderingGradients.h
+++ b/layout/painting/nsCSSRenderingGradients.h
@@ -31,60 +31,72 @@ struct ColorStop {
   double mPosition; // along the gradient line; 0=start, 1=end
   bool mIsMidpoint;
   gfx::Color mColor;
 };
 
 class nsCSSGradientRenderer final {
 public:
   /**
-   * Render a gradient for an element.
-   * aDest is the rect for a single tile of the gradient on the destination.
-   * aFill is the rect on the destination to be covered by repeated tiling of
-   * the gradient.
-   * aSrc is the part of the gradient to be rendered into a tile (aDest), if
-   * aSrc and aDest are different sizes, the image will be scaled to map aSrc
-   * onto aDest.
-   * aIntrinsicSize is the size of the source gradient.
+   * Prepare a nsCSSGradientRenderer for a gradient for an element.
+   * aIntrinsicSize - the size of the source gradient.
    */
-  static Maybe<nsCSSGradientRenderer> Create(nsPresContext* aPresContext,
-                                             nsStyleGradient* aGradient,
-                                             const nsRect& aDest,
-                                             const nsRect& aFill,
-                                             const nsSize& aRepeatSize,
-                                             const mozilla::CSSIntRect& aSrc,
-                                             const nsSize& aIntrinsiceSize);
+  static nsCSSGradientRenderer Create(nsPresContext* aPresContext,
+                                      nsStyleGradient* aGradient,
+                                      const nsSize& aIntrinsiceSize);
 
+  /**
+   * Draw the gradient to aContext
+   * aDest - where the first tile of gradient is
+   * aFill - the area to be filled with tiles of aDest
+   * aSrc - the area of the gradient that will fill aDest
+   * aRepeatSize - the distance from the origin of a tile
+   *               to the next origin of a tile
+   * aDirtyRect - pixels outside of this area may be skipped
+   */
   void Paint(gfxContext& aContext,
+             const nsRect& aDest,
+             const nsRect& aFill,
+             const nsSize& aRepeatSize,
+             const mozilla::CSSIntRect& aSrc,
              const nsRect& aDirtyRect,
              float aOpacity = 1.0);
 
+  /**
+   * Collect the gradient parameters
+   */
   void BuildWebRenderParameters(float aOpacity,
                                 WrGradientExtendMode& aMode,
                                 nsTArray<WrGradientStop>& aStops,
                                 LayoutDevicePoint& aLineStart,
                                 LayoutDevicePoint& aLineEnd,
                                 LayoutDeviceSize& aGradientRadius);
 
+  /**
+   * Build display items for the gradient
+   * aLayer - the layer to make this display item relative to
+   * aDest - where the first tile of gradient is
+   * aFill - the area to be filled with tiles of aDest
+   * aRepeatSize - the distance from the origin of a tile
+   *               to the next origin of a tile
+   * aSrc - the area of the gradient that will fill aDest
+   */
   void BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder,
                                   layers::WebRenderDisplayItemLayer* aLayer,
+                                  const nsRect& aDest,
+                                  const nsRect& aFill,
+                                  const nsSize& aRepeatSize,
+                                  const mozilla::CSSIntRect& aSrc,
                                   float aOpacity = 1.0);
 
 private:
   nsCSSGradientRenderer() {}
 
   nsPresContext* mPresContext;
   nsStyleGradient* mGradient;
-  CSSIntRect mSrc;
-  nsRect mDest;
-  nsRect mDirtyRect;
-  nsRect mFillArea;
-  nsSize mRepeatSize;
   nsTArray<ColorStop> mStops;
   gfxPoint mLineStart, mLineEnd;
   double mRadiusX, mRadiusY;
-  bool mForceRepeatToCoverTiles;
-  bool mForceRepeatToCoverTilesFlip;
 };
 
 } // namespace mozilla
 
 #endif /* nsCSSRenderingGradients_h__ */
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -4800,30 +4800,26 @@ nsDisplayBorder::CreateBorderImageWebRen
                                wr::ToWrSideOffsets2Df32(outset[0], outset[1], outset[2], outset[3]),
                                wr::ToWrRepeatMode(mBorderImageRenderer->mRepeatModeHorizontal),
                                wr::ToWrRepeatMode(mBorderImageRenderer->mRepeatModeVertical));
       break;
     }
     case eStyleImageType_Gradient:
     {
       RefPtr<nsStyleGradient> gradientData = mBorderImageRenderer->mImageRenderer.GetGradientData();
-      Maybe<nsCSSGradientRenderer> renderer =
+      nsCSSGradientRenderer renderer =
         nsCSSGradientRenderer::Create(mFrame->PresContext(), gradientData,
-                                      mBorderImageRenderer->mArea,
-                                      mBorderImageRenderer->mArea,
-                                      mBorderImageRenderer->mArea.Size(),
-                                      CSSIntRect(),
                                       mBorderImageRenderer->mImageSize);
 
       WrGradientExtendMode extendMode;
       nsTArray<WrGradientStop> stops;
       LayoutDevicePoint lineStart;
       LayoutDevicePoint lineEnd;
       LayoutDeviceSize gradientRadius;
-      renderer->BuildWebRenderParameters(1.0, extendMode, stops, lineStart, lineEnd, gradientRadius);
+      renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart, lineEnd, gradientRadius);
 
       if (gradientData->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
         Point startPoint = dest.TopLeft();
         startPoint = startPoint + Point(lineStart.x, lineStart.y);
         Point endPoint = dest.TopLeft();
         endPoint = endPoint + Point(lineEnd.x, lineEnd.y);
 
         aBuilder.PushBorderGradient(wr::ToWrRect(dest),
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -520,23 +520,20 @@ nsImageRenderer::Draw(nsPresContext*    
                                            aDest, aFill, aRepeatSize,
                                            aAnchor, aDirtyRect,
                                            ConvertImageRendererToDrawFlags(mFlags),
                                            mExtendMode, aOpacity);
       break;
     }
     case eStyleImageType_Gradient:
     {
-      Maybe<nsCSSGradientRenderer> renderer =
-        nsCSSGradientRenderer::Create(aPresContext, mGradientData,
-                                      aDest, aFill, aRepeatSize, aSrc, mSize);
+      nsCSSGradientRenderer renderer =
+        nsCSSGradientRenderer::Create(aPresContext, mGradientData, mSize);
 
-      if (renderer) {
-        renderer->Paint(*ctx, aDirtyRect, aOpacity);
-      }
+      renderer.Paint(*ctx, aDest, aFill, aRepeatSize, aSrc, aDirtyRect, aOpacity);
       break;
     }
     case eStyleImageType_Element:
     {
       RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx);
       if (!drawable) {
         NS_WARNING("Could not create drawable for element");
         return DrawResult::TEMPORARY_ERROR;
@@ -600,23 +597,20 @@ nsImageRenderer::BuildWebRenderDisplayIt
   if (aDest.IsEmpty() || aFill.IsEmpty() ||
       mSize.width <= 0 || mSize.height <= 0) {
     return;
   }
 
   switch (mType) {
     case eStyleImageType_Gradient:
     {
-      Maybe<nsCSSGradientRenderer> renderer =
-        nsCSSGradientRenderer::Create(aPresContext, mGradientData,
-                                   aDest, aFill, aRepeatSize, aSrc, mSize);
+      nsCSSGradientRenderer renderer =
+        nsCSSGradientRenderer::Create(aPresContext, mGradientData, mSize);
 
-      if (renderer) {
-        renderer->BuildWebRenderDisplayItems(aBuilder, aLayer, aOpacity);
-      }
+      renderer.BuildWebRenderDisplayItems(aBuilder, aLayer, aDest, aFill, aRepeatSize, aSrc, aOpacity);
       break;
     }
     case eStyleImageType_Image:
     {
       RefPtr<layers::ImageContainer> container = mImageContainer->GetImageContainer(aLayer->WrManager(),
                                                                                     ConvertImageRendererToDrawFlags(mFlags));
       if (!container) {
         NS_WARNING("Failed to get image container");