Bug 761393 - Cache the gfxPatterns using an nsExpirationTracker and an hashtable. r=dbaron
authorPaul Adenot <paul@paul.cx>
Sun, 26 Aug 2012 21:09:46 -0700
changeset 105651 dd28317d64d4715d9b36e5a5f0be31f4cfcef25c
parent 105650 07a27119d9c983c88687f406e53ce6cabf4ae028
child 105652 1b3fa4309f3145d736a0a75762d9a1da7382d2c1
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersdbaron
bugs761393
milestone18.0a1
Bug 761393 - Cache the gfxPatterns using an nsExpirationTracker and an hashtable. r=dbaron
layout/base/nsCSSRendering.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -42,16 +42,17 @@
 #include "nsContentUtils.h"
 #include "nsSVGEffects.h"
 #include "nsSVGIntegrationUtils.h"
 #include "gfxDrawable.h"
 #include "sampler.h"
 #include "nsCSSRenderingBorders.h"
 #include "mozilla/css/ImageLoader.h"
 #include "ImageContainer.h"
+#include "mozilla/HashFunctions.h"
 
 using namespace mozilla;
 using namespace mozilla::css;
 
 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
 // recalculating this for each frame in a continuation (perf), hold
 // a cache of various coordinate information that we need in order
 // to paint inline backgrounds.
@@ -266,16 +267,158 @@ protected:
       // Make sure aFrame1 and aFrame2 are in the same continuation of
       // mBlockFrame.
       it1.GetContainer() == it2.GetContainer() &&
       // And on the same line in it
       it1.GetLine() == it2.GetLine();
   }
 };
 
+struct GradientCacheKey : public PLDHashEntryHdr {
+  typedef const GradientCacheKey& KeyType;
+  typedef const GradientCacheKey* KeyTypePointer;
+  enum { ALLOW_MEMMOVE = true };
+  const nsRefPtr<nsStyleGradient> mGradient;
+  const gfxSize mGradientSize;
+
+  GradientCacheKey(nsStyleGradient* aGradient, const gfxSize& aGradientSize)
+    : mGradient(aGradient), mGradientSize(aGradientSize)
+  { }
+
+  GradientCacheKey(const GradientCacheKey* aOther)
+    : mGradient(aOther->mGradient), mGradientSize(aOther->mGradientSize)
+  { }
+
+  static PLDHashNumber
+  HashKey(const KeyTypePointer aKey)
+  {
+    PLDHashNumber hash = 0;
+    hash = AddToHash(hash, aKey->mGradientSize.width);
+    hash = AddToHash(hash, aKey->mGradientSize.height);
+    hash = aKey->mGradient->Hash(hash);
+    return hash;
+  }
+
+  bool KeyEquals(KeyTypePointer aKey) const
+  {
+    return (*aKey->mGradient == *mGradient) &&
+           (aKey->mGradientSize == mGradientSize);
+  }
+  static KeyTypePointer KeyToPointer(KeyType aKey)
+  {
+    return &aKey;
+  }
+};
+
+/**
+ * This class is what is cached. It need to be allocated in an object separated
+ * to the cache entry to be able to be tracked by the nsExpirationTracker.
+ * */
+struct GradientCacheData {
+  GradientCacheData(gfxPattern* aPattern, bool aCoversTile, GradientCacheKey aKey)
+    : mPattern(aPattern), mCoversTile(aCoversTile), mKey(aKey)
+  {}
+
+  GradientCacheData(const GradientCacheData& aOther)
+    : mPattern(aOther.mPattern),
+      mCoversTile(aOther.mCoversTile),
+      mKey(aOther.mKey)
+  { }
+
+  nsExpirationState *GetExpirationState() {
+    return &mExpirationState;
+  }
+
+  nsExpirationState mExpirationState;
+  nsRefPtr<gfxPattern> mPattern;
+  bool mCoversTile;
+  GradientCacheKey mKey;
+};
+
+/**
+ * This class implements a cache with no maximum size, that retains the
+ * gfxPatterns used to draw the gradients.
+ *
+ * The key is the nsStyleGradient that defines the gradient, and the size of the
+ * gradient.
+ *
+ * The value is the gfxPattern, and whether or not we perform an optimization
+ * based on the actual gradient property.
+ *
+ * An entry stays in the cache as long as it is used often. As long as a cache
+ * entry is in the cache, all the references it has are guaranteed to be valid:
+ * the nsStyleRect for the key, the gfxPattern for the value.
+ */
+class GradientCache MOZ_FINAL : public nsExpirationTracker<GradientCacheData,4>
+{
+  public:
+    enum { MAX_GENERATION_MS = 10000};
+
+    GradientCache()
+      : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS)
+    {
+      mHashEntries.Init();
+    }
+
+    virtual void NotifyExpired(GradientCacheData* aObject)
+    {
+      // This will free the gfxPattern.
+      RemoveObject(aObject);
+      mHashEntries.Remove(aObject->mKey);
+    }
+
+    GradientCacheData* Lookup(nsStyleGradient* aKey, const gfxSize& aGradientSize)
+    {
+      // We don't cache gradient that have Calc value, because the Calc object
+      // can be deallocated by the time we want to compute the hash, and thus we
+      // would have a dangling pointer in some nsStyleCoord in the
+      // nsStyleGradient that are in the hash table.
+      if (aKey->HasCalc()) {
+        return nullptr;
+      }
+
+      GradientCacheData* gradient = mHashEntries.Get(GradientCacheKey(aKey, aGradientSize));
+
+      if (gradient) {
+        MarkUsed(gradient);
+      }
+
+      return gradient;
+    }
+
+    // Returns true if we successfully register the gradient in the cache, false
+    // otherwise.
+    bool RegisterEntry(nsStyleGradient* aKey, const gfxSize& aGradientSize, GradientCacheData* aValue)
+    {
+      // We don't cache gradient that have Calc values (see
+      // GradientCache::Lookup).
+      if (aKey->HasCalc()) {
+        return false;
+      }
+      nsresult rv = AddObject(aValue);
+      if (NS_FAILED(rv)) {
+        // We are OOM, and we cannot track this object. We don't want stall
+        // entries in the hash table (since the expiration tracker is responsible
+        // for removing the cache entries), so we avoid putting that entry in the
+        // table, which is a good things considering we are short on memory
+        // anyway, we probably don't want to retain things.
+        return false;
+      }
+      mHashEntries.Put(GradientCacheKey(aKey, aGradientSize), aValue);
+      return true;
+    }
+
+  protected:
+    /**
+     * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
+     * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
+     */
+    nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
+};
+
 /* Local functions */
 static void DrawBorderImage(nsPresContext* aPresContext,
                             nsRenderingContext& aRenderingContext,
                             nsIFrame* aForFrame,
                             const nsRect& aBorderArea,
                             const nsStyleBorder& aStyleBorder,
                             const nsRect& aDirtyRect);
 
@@ -291,29 +434,33 @@ static void DrawBorderImageComponent(nsR
                                      const nsStyleBorder& aStyleBorder,
                                      uint8_t aIndex);
 
 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
                               nscolor aBackgroundColor,
                               nscolor aBorderColor);
 
 static InlineBackgroundData* gInlineBGData = nullptr;
+static GradientCache* gGradientCache = nullptr;
 
 // Initialize any static variables used by nsCSSRendering.
 void nsCSSRendering::Init()
 {
   NS_ASSERTION(!gInlineBGData, "Init called twice");
   gInlineBGData = new InlineBackgroundData();
+  gGradientCache = new GradientCache();
 }
 
 // Clean up any global variables used by nsCSSRendering.
 void nsCSSRendering::Shutdown()
 {
   delete gInlineBGData;
   gInlineBGData = nullptr;
+  delete gGradientCache;
+  gGradientCache = nullptr;
 }
 
 /**
  * Make a bevel color
  */
 static nscolor
 MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
                nscolor aBackgroundColor, nscolor aBorderColor)
@@ -1853,251 +2000,259 @@ nsCSSRendering::PaintGradient(nsPresCont
   if (aOneCellArea.IsEmpty())
     return;
 
   gfxContext *ctx = aRenderingContext.ThebesContext();
   nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
   gfxRect oneCellArea =
     nsLayoutUtils::RectToGfxRect(aOneCellArea, appUnitsPerPixel);
 
-  // Compute "gradient line" start and end relative to oneCellArea
-  gfxPoint lineStart, lineEnd;
-  double radiusX = 0, radiusY = 0; // for radial gradients only
-  if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
-    ComputeLinearGradientLine(aPresContext, aGradient, oneCellArea.Size(),
-                              &lineStart, &lineEnd);
-  } else {
-    ComputeRadialGradientLine(aPresContext, aGradient, oneCellArea.Size(),
-                              &lineStart, &lineEnd, &radiusX, &radiusY);
-  }
-  gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
-                                 lineEnd.y - lineStart.y);
-
-  NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2,
-                    "The parser should reject gradients with less than two stops");
-
-  // Build color stop array and compute stop positions
-  nsTArray<ColorStop> stops;
-  // If there is a run of stops before stop i that did not have specified
-  // positions, then this is the index of the first stop in that run, otherwise
-  // it's -1.
-  int32_t firstUnsetPosition = -1;
-  for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
-    const nsStyleGradientStop& stop = aGradient->mStops[i];
-    double position;
-    switch (stop.mLocation.GetUnit()) {
-    case eStyleUnit_None:
-      if (i == 0) {
-        // First stop defaults to position 0.0
-        position = 0.0;
-      } else if (i == aGradient->mStops.Length() - 1) {
-        // Last stop defaults to position 1.0
-        position = 1.0;
-      } else {
-        // Other stops with no specified position get their position assigned
-        // later by interpolation, see below.
-        // Remeber where the run of stops with no specified position starts,
-        // if it starts here.
-        if (firstUnsetPosition < 0) {
-          firstUnsetPosition = i;
-        }
-        stops.AppendElement(ColorStop(0, stop.mColor));
-        continue;
-      }
-      break;
-    case eStyleUnit_Percent:
-      position = stop.mLocation.GetPercentValue();
-      break;
-    case eStyleUnit_Coord:
-      position = lineLength < 1e-6 ? 0.0 :
-          stop.mLocation.GetCoordValue() / appUnitsPerPixel / lineLength;
-      break;
-    default:
-      NS_ABORT_IF_FALSE(false, "Unknown stop position type");
-    }
-
-    if (i > 0) {
-      // Prevent decreasing stop positions by advancing this position
-      // to the previous stop position, if necessary
-      position = NS_MAX(position, stops[i - 1].mPosition);
+  bool gradientRegistered = true;
+  GradientCacheData* pattern = gGradientCache->Lookup(aGradient, oneCellArea.Size());
+
+  if (pattern == nullptr) {
+    // Compute "gradient line" start and end relative to oneCellArea
+    gfxPoint lineStart, lineEnd;
+    double radiusX = 0, radiusY = 0; // for radial gradients only
+    if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
+      ComputeLinearGradientLine(aPresContext, aGradient, oneCellArea.Size(),
+                                &lineStart, &lineEnd);
+    } else {
+      ComputeRadialGradientLine(aPresContext, aGradient, oneCellArea.Size(),
+                                &lineStart, &lineEnd, &radiusX, &radiusY);
     }
-    stops.AppendElement(ColorStop(position, stop.mColor));
-    if (firstUnsetPosition > 0) {
-      // Interpolate positions for all stops that didn't have a specified position
-      double p = stops[firstUnsetPosition - 1].mPosition;
-      double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
-      for (uint32_t j = firstUnsetPosition; j < i; ++j) {
-        p += d;
-        stops[j].mPosition = p;
+    gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
+                                   lineEnd.y - lineStart.y);
+
+    NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2,
+                      "The parser should reject gradients with less than two stops");
+
+    // Build color stop array and compute stop positions
+    nsTArray<ColorStop> stops;
+    // If there is a run of stops before stop i that did not have specified
+    // positions, then this is the index of the first stop in that run, otherwise
+    // it's -1.
+    int32_t firstUnsetPosition = -1;
+    for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
+      const nsStyleGradientStop& stop = aGradient->mStops[i];
+      double position;
+      switch (stop.mLocation.GetUnit()) {
+      case eStyleUnit_None:
+        if (i == 0) {
+          // First stop defaults to position 0.0
+          position = 0.0;
+        } else if (i == aGradient->mStops.Length() - 1) {
+          // Last stop defaults to position 1.0
+          position = 1.0;
+        } else {
+          // Other stops with no specified position get their position assigned
+          // later by interpolation, see below.
+          // Remeber where the run of stops with no specified position starts,
+          // if it starts here.
+          if (firstUnsetPosition < 0) {
+            firstUnsetPosition = i;
+          }
+          stops.AppendElement(ColorStop(0, stop.mColor));
+          continue;
+        }
+        break;
+      case eStyleUnit_Percent:
+        position = stop.mLocation.GetPercentValue();
+        break;
+      case eStyleUnit_Coord:
+        position = lineLength < 1e-6 ? 0.0 :
+            stop.mLocation.GetCoordValue() / appUnitsPerPixel / lineLength;
+        break;
+      default:
+        NS_ABORT_IF_FALSE(false, "Unknown stop position type");
       }
-      firstUnsetPosition = -1;
+
+      if (i > 0) {
+        // Prevent decreasing stop positions by advancing this position
+        // to the previous stop position, if necessary
+        position = NS_MAX(position, stops[i - 1].mPosition);
+      }
+      stops.AppendElement(ColorStop(position, stop.mColor));
+      if (firstUnsetPosition > 0) {
+        // Interpolate positions for all stops that didn't have a specified position
+        double p = stops[firstUnsetPosition - 1].mPosition;
+        double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
+        for (uint32_t j = firstUnsetPosition; j < i; ++j) {
+          p += d;
+          stops[j].mPosition = p;
+        }
+        firstUnsetPosition = -1;
+      }
     }
-  }
-
-  // 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) {
-      // Choose an instance of the repeated pattern that gives us all positive
-      // stop-offsets.
-      double lastStop = stops[stops.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;
+
+    // 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) {
+        // Choose an instance of the repeated pattern that gives us all positive
+        // stop-offsets.
+        double lastStop = stops[stops.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;
+          }
         }
-      }
-    } 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;
-        if (pos < 0.0) {
-          stops[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 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!
-              double frac = (0.0 - pos)/(nextPos - pos);
-              stops[i].mColor =
-                InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
+      } 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;
+          if (pos < 0.0) {
+            stops[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 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!
+                double frac = (0.0 - pos)/(nextPos - pos);
+                stops[i].mColor =
+                  InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
+              }
             }
           }
         }
       }
-    }
-    firstStop = stops[0].mPosition;
-    NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets");
-  }
-
-  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 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.
-    stopScale = 0.0;
-    if (aGradient->mRepeating || zeroRadius) {
-      radiusX = radiusY = 0.0;
+      firstStop = stops[0].mPosition;
+      NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets");
     }
-    lastStop = firstStop;
-  } else {
-    stopScale = 1.0/stopDelta;
-  }
-
-  // Create the gradient pattern.
-  nsRefPtr<gfxPattern> gradientPattern;
-  bool forceRepeatToCoverTiles = false;
-  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.
-    gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*firstStop;
-    gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*lastStop;
-
-    if (stopScale == 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);
+
+    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 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.
+      stopScale = 0.0;
+      if (aGradient->mRepeating || zeroRadius) {
+        radiusX = radiusY = 0.0;
+      }
+      lastStop = firstStop;
+    } else {
+      stopScale = 1.0/stopDelta;
     }
 
-    gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
-                                     gradientEnd.x, gradientEnd.y);
-
-    // When the gradient line is parallel to the x axis from the left edge
-    // to the right edge of a tile, then we can repeat by just repeating the
-    // gradient.
-    if ((gradientStart.y == gradientEnd.y && gradientStart.x == 0 &&
-         gradientEnd.x == oneCellArea.width) ||
-        (gradientStart.x == gradientEnd.x && gradientStart.y == 0 &&
-         gradientEnd.y == oneCellArea.height)) {
-      forceRepeatToCoverTiles = true;
-    }
-  } 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*firstStop;
-    double outerRadius = radiusX*lastStop;
-    if (stopScale == 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;
+    // Create the gradient pattern.
+    nsRefPtr<gfxPattern> gradientPattern;
+    bool forceRepeatToCoverTiles = false;
+    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.
+      gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*firstStop;
+      gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*lastStop;
+
+      if (stopScale == 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);
+      }
+
+      gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
+                                       gradientEnd.x, gradientEnd.y);
+
+      // When the gradient line is parallel to the x axis from the left edge
+      // to the right edge of a tile, then we can repeat by just repeating the
+      // gradient.
+      if ((gradientStart.y == gradientEnd.y && gradientStart.x == 0 &&
+           gradientEnd.x == oneCellArea.width) ||
+          (gradientStart.x == gradientEnd.x && gradientStart.y == 0 &&
+           gradientEnd.y == oneCellArea.height)) {
+        forceRepeatToCoverTiles = true;
+      }
+    } 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*firstStop;
+      double outerRadius = radiusX*lastStop;
+      if (stopScale == 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.
+        gfxMatrix matrix;
+        matrix.Translate(lineStart);
+        matrix.Scale(1.0, radiusX/radiusY);
+        matrix.Translate(-lineStart);
+        gradientPattern->SetMatrix(matrix);
+      }
     }
-    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.
-      gfxMatrix matrix;
-      matrix.Translate(lineStart);
-      matrix.Scale(1.0, radiusX/radiusY);
-      matrix.Translate(-lineStart);
-      gradientPattern->SetMatrix(matrix);
+    if (gradientPattern->CairoStatus())
+      return;
+
+    // Now set normalized color stops in pattern.
+    if (stopScale == 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.
+      if (!aGradient->mRepeating && !zeroRadius) {
+        gradientPattern->AddColorStop(0.0, stops[0].mColor);
+      }
+      gradientPattern->AddColorStop(0.0, stops[stops.Length() - 1].mColor);
+    } else {
+      // Use all stops
+      for (uint32_t i = 0; i < stops.Length(); i++) {
+        double pos = stopScale*(stops[i].mPosition - firstStop);
+        gradientPattern->AddColorStop(pos, stops[i].mColor);
+      }
     }
-  }
-  if (gradientPattern->CairoStatus())
-    return;
-
-  // Now set normalized color stops in pattern.
-  if (stopScale == 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.
-    if (!aGradient->mRepeating && !zeroRadius) {
-      gradientPattern->AddColorStop(0.0, stops[0].mColor);
+
+    // Set repeat mode. Default cairo extend mode is PAD.
+    if (aGradient->mRepeating || forceRepeatToCoverTiles) {
+      gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT);
     }
-    gradientPattern->AddColorStop(0.0, stops[stops.Length() - 1].mColor);
-  } else {
-    // Use all stops
-    for (uint32_t i = 0; i < stops.Length(); i++) {
-      double pos = stopScale*(stops[i].mPosition - firstStop);
-      gradientPattern->AddColorStop(pos, stops[i].mColor);
-    }
-  }
-
-  // Set repeat mode. Default cairo extend mode is PAD.
-  if (aGradient->mRepeating || forceRepeatToCoverTiles) {
-    gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT);
+    // Register the gradient newly computed in the cache.
+    pattern = new GradientCacheData(gradientPattern, forceRepeatToCoverTiles, GradientCacheKey(aGradient, oneCellArea.Size()));
+    gradientRegistered = gGradientCache->RegisterEntry(aGradient, oneCellArea.Size(), pattern);
   }
 
   // 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))
@@ -2105,30 +2260,30 @@ nsCSSRendering::PaintGradient(nsPresCont
 
   gfxRect areaToFill =
     nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerPixel);
   gfxMatrix ctm = ctx->CurrentMatrix();
 
   // xStart/yStart are the top-left corner of the top-left tile.
   nscoord xStart = FindTileStart(dirty.x, aOneCellArea.x, aOneCellArea.width);
   nscoord yStart = FindTileStart(dirty.y, aOneCellArea.y, aOneCellArea.height);
-  nscoord xEnd = forceRepeatToCoverTiles ? xStart + aOneCellArea.width : dirty.XMost();
-  nscoord yEnd = forceRepeatToCoverTiles ? yStart + aOneCellArea.height : dirty.YMost();
+  nscoord xEnd = pattern->mCoversTile ? xStart + aOneCellArea.width : dirty.XMost();
+  nscoord yEnd = pattern->mCoversTile ? yStart + aOneCellArea.height : dirty.YMost();
 
   // x and y are the top-left corner of the tile to draw
   for (nscoord y = yStart; y < yEnd; y += aOneCellArea.height) {
     for (nscoord x = xStart; x < xEnd; x += aOneCellArea.width) {
       // The coordinates of the tile
       gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
                       nsRect(x, y, aOneCellArea.width, aOneCellArea.height),
                       appUnitsPerPixel);
       // 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);
+        pattern->mCoversTile ? areaToFill : tileRect.Intersect(areaToFill);
       ctx->NewPath();
       // If we can snap the gradient tile and fill rects, do so, but make sure
       // that the gradient is scaled precisely to the tile rect.
       gfxRect fillRectSnapped = fillRect;
       // Don't snap the tileRect directly since that would lose information
       // about the orientation of the current transform (i.e. vertical or
       // horizontal flipping). Instead snap the corners independently so if
       // the CTM has a flip, our Scale() below preserves the flip.
@@ -2141,21 +2296,26 @@ nsCSSRendering::PaintGradient(nsPresCont
         ctx->Rectangle(fillRectSnapped);
         ctx->Translate(tileRectSnappedTopLeft);
         ctx->Scale((tileRectSnappedBottomRight.x - tileRectSnappedTopLeft.x)/tileRect.width,
                    (tileRectSnappedBottomRight.y - tileRectSnappedTopLeft.y)/tileRect.height);
       } else {
         ctx->Rectangle(fillRect);
         ctx->Translate(tileRect.TopLeft());
       }
-      ctx->SetPattern(gradientPattern);
+      ctx->SetPattern(pattern->mPattern);
       ctx->Fill();
       ctx->SetMatrix(ctm);
     }
   }
+  // If we could not put the gradient in the gradient cache, make sure to
+  // release its resources so we don't leak.
+  if (!gradientRegistered) {
+    delete pattern;
+  }
 }
 
 void
 nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
                                       nsRenderingContext& aRenderingContext,
                                       nsIFrame* aForFrame,
                                       const nsRect& aDirtyRect,
                                       const nsRect& aBorderArea,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1443,16 +1443,43 @@ nsStyleGradient::IsOpaque()
 {
   for (uint32_t i = 0; i < mStops.Length(); i++) {
     if (NS_GET_A(mStops[i].mColor) < 255)
       return false;
   }
   return true;
 }
 
+bool
+nsStyleGradient::HasCalc()
+{
+  // The stops cannot have a Calc.
+  return mBgPosX.IsCalcUnit() || mBgPosY.IsCalcUnit() || mAngle.IsCalcUnit() ||
+         mRadiusX.IsCalcUnit() || mRadiusX.IsCalcUnit();
+}
+
+uint32_t
+nsStyleGradient::Hash(PLDHashNumber aHash)
+{
+  aHash = mozilla::AddToHash(aHash, mShape);
+  aHash = mozilla::AddToHash(aHash, mSize);
+  aHash = mozilla::AddToHash(aHash, mRepeating);
+  aHash = mozilla::AddToHash(aHash, mLegacySyntax);
+  aHash = mBgPosX.HashValue(aHash);
+  aHash = mBgPosY.HashValue(aHash);
+  aHash = mAngle.HashValue(aHash);
+  aHash = mRadiusX.HashValue(aHash);
+  aHash = mRadiusY.HashValue(aHash);
+  for (uint32_t i = 0; i < mStops.Length(); i++) {
+    aHash = mStops[i].mLocation.HashValue(aHash);
+    aHash = mozilla::AddToHash(aHash, mStops[i].mColor);
+  }
+  return aHash;
+}
+
 // --------------------
 // nsStyleImage
 //
 
 nsStyleImage::nsStyleImage()
   : mType(eStyleImageType_Null)
   , mCropRect(nullptr)
 #ifdef DEBUG
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -144,16 +144,18 @@ public:
   nsTArray<nsStyleGradientStop> mStops;
 
   bool operator==(const nsStyleGradient& aOther) const;
   bool operator!=(const nsStyleGradient& aOther) const {
     return !(*this == aOther);
   }
 
   bool IsOpaque();
+  bool HasCalc();
+  uint32_t Hash(PLDHashNumber aHash);
 
   NS_INLINE_DECL_REFCOUNTING(nsStyleGradient)
 
 private:
   ~nsStyleGradient() {}
 
   nsStyleGradient(const nsStyleGradient& aOther) MOZ_DELETE;
   nsStyleGradient& operator=(const nsStyleGradient& aOther) MOZ_DELETE;