Bug 1341101 part 2 - Create a nsCSSGradientRenderer for collecting gradient parameters r=mattwoodrow
authorRyan Hunt <rhunt@eqrion.net>
Wed, 15 Mar 2017 03:14:17 -0400
changeset 350268 d82968c4e2921cfb4ca5a7f746c8ac46f15dd370
parent 350267 2d4b0a9b2d356555abf2dcb577019dce6c73d105
child 350269 5f5fef49b82c0c97bc403c8936f3ff7800416808
push id31570
push userryanvm@gmail.com
push dateWed, 29 Mar 2017 13:42:06 +0000
treeherdermozilla-central@6ea713ccc9ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1341101
milestone55.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 1341101 part 2 - Create a nsCSSGradientRenderer for collecting gradient parameters r=mattwoodrow Refactor the existing PaintGradient method into a nsCSSGradientRenderer class. The purpose of this is to separate the calculation of gradient parameters from the painting of the gradients. This should help us to share code between WebRender and our existing painting code. It does not change how we render gradients currently. MozReview-Commit-ID: 4P8vPqK4V8g
layout/painting/nsCSSRenderingGradients.cpp
layout/painting/nsCSSRenderingGradients.h
layout/painting/nsImageRenderer.cpp
--- a/layout/painting/nsCSSRenderingGradients.cpp
+++ b/layout/painting/nsCSSRenderingGradients.cpp
@@ -27,28 +27,16 @@
 #include "nsCSSProps.h"
 #include "mozilla/Telemetry.h"
 #include "gfxUtils.h"
 #include "gfxGradientCache.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
-// A resolved color stop, with a specific position along the gradient line and
-// a color.
-struct ColorStop {
-  ColorStop(): mPosition(0), mIsMidpoint(false) {}
-  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;
-};
-
-
 static gfxFloat
 ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
                              gfxFloat aFillLength,
                              int32_t aAppUnitsPerPixel)
 {
   switch (aCoord.GetUnit()) {
     case eStyleUnit_Percent:
       return aCoord.GetPercentValue() * aFillLength;
@@ -540,34 +528,27 @@ ClampColorStops(nsTArray<ColorStop>& aSt
   }
   if (aStops.LastElement().mPosition < 1) {
     aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
   }
 }
 
 namespace mozilla {
 
-void
-nsCSSGradientRenderer::Paint(nsPresContext* aPresContext,
-                             gfxContext& aContext,
+Maybe<nsCSSGradientRenderer>
+nsCSSGradientRenderer::Create(nsPresContext* aPresContext,
                              nsStyleGradient* aGradient,
-                             const nsRect& aDirtyRect,
                              const nsRect& aDest,
                              const nsRect& aFillArea,
                              const nsSize& aRepeatSize,
                              const CSSIntRect& aSrc,
-                             const nsSize& aIntrinsicSize,
-                             float aOpacity)
+                             const nsSize& aIntrinsicSize)
 {
-  PROFILER_LABEL("nsCSSRendering", "PaintGradient",
-    js::ProfileEntry::Category::GRAPHICS);
-
-  Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
-    return;
+    return Nothing();
   }
 
   nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
   gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
                             gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
 
   bool cellContainsFill = aDest.Contains(aFillArea);
 
@@ -662,30 +643,30 @@ nsCSSGradientRenderer::Paint(nsPresConte
   // 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) &&
     aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
     !aGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
+  bool forceRepeatToCoverTilesFlip = false;
 
-  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);
-      matrix.Scale(-1, -1);
+      forceRepeatToCoverTilesFlip = true;
     }
 
     // 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);
@@ -764,189 +745,222 @@ nsCSSGradientRenderer::Paint(nsPresConte
           }
         }
       }
     }
     firstStop = stops[0].mPosition;
     MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
   }
 
-  if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->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 = stops[stops.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 = 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;
     }
-    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 (!aGradient->mRepeating || stopDelta == 0.0) {
-    stopOrigin = std::min(stopOrigin, 0.0);
-    stopEnd = std::max(stopEnd, 1.0);
-  }
-  stopScale = 1.0/(stopEnd - stopOrigin);
-
-  // Create the gradient pattern.
-  RefPtr<gfxPattern> gradientPattern;
-  gfxPoint gradientStart;
-  gfxPoint gradientEnd;
-  if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
-    // Compute the actual gradient line ends we need to pass to cairo after
-    // stops have been normalized.
-    gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
-    gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
-    gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
-    gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
-
-    if (stopDelta == 0.0) {
-      // Stops are all at the same place. For repeating gradients, this will
-      // just paint the last stop color. We don't need to do anything.
-      // For non-repeating gradients, this should render as two colors, one
-      // on each "side" of the gradient line segment, which is a point. All
-      // our stops will be at 0.0; we just need to set the direction vector
-      // correctly.
-      gradientEnd = gradientStart + (lineEnd - lineStart);
-      gradientStopEnd = gradientStopStart + (lineEnd - lineStart);
-    }
-
-    gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
-                                      gradientEnd.x, gradientEnd.y);
-  } else {
-    NS_ASSERTION(firstStop >= 0.0,
-                  "Negative stops not allowed for radial gradients");
-
-    // To form an ellipse, we'll stretch a circle vertically, if necessary.
-    // So our radii are based on radiusX.
-    double innerRadius = radiusX*stopOrigin;
-    double outerRadius = radiusX*stopEnd;
-    if (stopDelta == 0.0) {
-      // Stops are all at the same place.  See above (except we now have
-      // the inside vs. outside of an ellipse).
-      outerRadius = innerRadius + 1;
-    }
-    gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius,
-                                     lineStart.x, lineStart.y, outerRadius);
-    if (radiusX != radiusY) {
-      // Stretch the circles into ellipses vertically by setting a transform
-      // in the pattern.
-      // Recall that this is the transform from user space to pattern space.
-      // So to stretch the ellipse by factor of P vertically, we scale
-      // user coordinates by 1/P.
-      matrix.Translate(lineStart);
-      matrix.Scale(1.0, radiusX/radiusY);
-      matrix.Translate(-lineStart);
-    }
-  }
-  // Use a pattern transform to take account of source and dest rects
-  matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
-                            aPresContext->CSSPixelsToDevPixels(aSrc.y)));
-  matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
-               gfxFloat(aPresContext->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(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);
-  ResolvePremultipliedAlpha(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;
+  }
+
+  // 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;
+
+  // 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);
 
-  bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles;
+  // 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;
+    gradientEnd = mLineStart + (mLineEnd - mLineStart)*stopEnd;
+
+    if (stopDelta == 0.0) {
+      // Stops are all at the same place. For repeating gradients, this will
+      // just paint the last stop color. We don't need to do anything.
+      // For non-repeating gradients, this should render as two colors, one
+      // on each "side" of the gradient line segment, which is a point. All
+      // our stops will be at 0.0; we just need to set the direction vector
+      // correctly.
+      gradientEnd = gradientStart + (mLineEnd - mLineStart);
+    }
+
+    gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
+                                      gradientEnd.x, gradientEnd.y);
+  } else {
+    NS_ASSERTION(firstStop >= 0.0,
+                  "Negative stops not allowed for radial gradients");
+
+    // To form an ellipse, we'll stretch a circle vertically, if necessary.
+    // So our radii are based on radiusX.
+    double innerRadius = mRadiusX*stopOrigin;
+    double outerRadius = mRadiusX*stopEnd;
+    if (stopDelta == 0.0) {
+      // Stops are all at the same place.  See above (except we now have
+      // the inside vs. outside of an ellipse).
+      outerRadius = innerRadius + 1;
+    }
+    gradientPattern = new gfxPattern(mLineStart.x, mLineStart.y, innerRadius,
+                                     mLineStart.x, mLineStart.y, outerRadius);
+    if (mRadiusX != mRadiusY) {
+      // Stretch the circles into ellipses vertically by setting a transform
+      // in the pattern.
+      // Recall that this is the transform from user space to pattern space.
+      // 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);
+  gradientPattern->SetMatrix(matrix);
+
+  ResolvePremultipliedAlpha(mStops);
+
+  bool isRepeat = mGradient->mRepeating || mForceRepeatToCoverTiles;
 
   // 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).
-  nsTArray<gfx::GradientStop> rawStops(stops.Length());
-  rawStops.SetLength(stops.Length());
-  for(uint32_t i = 0; i < stops.Length(); i++) {
-    rawStops[i].color = stops[i].mColor;
+  nsTArray<gfx::GradientStop> rawStops(mStops.Length());
+  rawStops.SetLength(mStops.Length());
+  for(uint32_t i = 0; i < mStops.Length(); i++) {
+    rawStops[i].color = mStops[i].mColor;
     rawStops[i].color.a *= aOpacity;
-    rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
+    rawStops[i].offset = stopScale * (mStops[i].mPosition - stopOrigin);
   }
   RefPtr<mozilla::gfx::GradientStops> gs =
     gfxGradientCache::GetOrCreateGradientStops(aContext.GetDrawTarget(),
                                                rawStops,
                                                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, aFillArea))
+  if (!dirty.IntersectRect(aDirtyRect, mFillArea))
     return;
 
   gfxRect areaToFill =
-    nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
+    nsLayoutUtils::RectToGfxRect(mFillArea, 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, 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();
+  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();
 
   // x and y are the top-left corner of the tile to draw
-  for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
-    for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
+  for (nscoord y = yStart; y < yEnd; y += mRepeatSize.height) {
+    for (nscoord x = xStart; x < xEnd; x += mRepeatSize.width) {
       // The coordinates of the tile
       gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
-                      nsRect(x, y, aDest.width, aDest.height),
+                      nsRect(x, y, mDest.width, mDest.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 =
-        forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
+        mForceRepeatToCoverTiles ? 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 &&
@@ -968,18 +982,18 @@ nsCSSGradientRenderer::Paint(nsPresConte
         aContext.SetMatrix(transform);
       }
       aContext.NewPath();
       aContext.Rectangle(fillRect);
 
       gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
       gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
       Color edgeColor;
-      if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
-          RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, stops,
+      if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
+          RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
                                          gradientStart, gradientEnd, &edgeColor)) {
         edgeColor.a *= aOpacity;
         aContext.SetColor(edgeColor);
       } else {
         aContext.SetMatrix(
           aContext.CurrentMatrix().Copy().Translate(tileRect.TopLeft()));
         aContext.SetPattern(gradientPattern);
       }
--- a/layout/painting/nsCSSRenderingGradients.h
+++ b/layout/painting/nsCSSRenderingGradients.h
@@ -4,38 +4,67 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsCSSRenderingGradients_h__
 #define nsCSSRenderingGradients_h__
 
 #include "nsLayoutUtils.h"
 #include "nsStyleStruct.h"
 #include "Units.h"
+#include "mozilla/Maybe.h"
 
 namespace mozilla {
 
+// A resolved color stop, with a specific position along the gradient line and
+// a color.
+struct ColorStop {
+  ColorStop(): mPosition(0), mIsMidpoint(false) {}
+  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;
+};
+
 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.
    */
-  static void Paint(nsPresContext* aPresContext,
-                    gfxContext& aContext,
-                    nsStyleGradient* aGradient,
-                    const nsRect& aDirtyRect,
-                    const nsRect& aDest,
-                    const nsRect& aFill,
-                    const nsSize& aRepeatSize,
-                    const mozilla::CSSIntRect& aSrc,
-                    const nsSize& aIntrinsiceSize,
-                    float aOpacity = 1.0);
+  static Maybe<nsCSSGradientRenderer> Create(nsPresContext* aPresContext,
+                                             nsStyleGradient* aGradient,
+                                             const nsRect& aDest,
+                                             const nsRect& aFill,
+                                             const nsSize& aRepeatSize,
+                                             const mozilla::CSSIntRect& aSrc,
+                                             const nsSize& aIntrinsiceSize);
+
+  void Paint(gfxContext& aContext,
+             const nsRect& aDirtyRect,
+             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;   // for radial gradients only
+  bool mForceRepeatToCoverTiles;
+  bool mForceRepeatToCoverTilesFlip;
 };
 
 } // namespace mozilla
 
 #endif /* nsCSSRenderingGradients_h__ */
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -504,20 +504,23 @@ nsImageRenderer::Draw(nsPresContext*    
                                            aDest, aFill, aRepeatSize,
                                            aAnchor, aDirtyRect,
                                            ConvertImageRendererToDrawFlags(mFlags),
                                            mExtendMode, aOpacity);
       break;
     }
     case eStyleImageType_Gradient:
     {
-      nsCSSGradientRenderer::Paint(aPresContext, *ctx,
-                                   mGradientData, aDirtyRect,
-                                   aDest, aFill, aRepeatSize, aSrc, mSize,
-                                   aOpacity);
+      Maybe<nsCSSGradientRenderer> renderer =
+        nsCSSGradientRenderer::Create(aPresContext, mGradientData,
+                                      aDest, aFill, aRepeatSize, aSrc, mSize);
+
+      if (renderer) {
+        renderer->Paint(*ctx, 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;