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 109916 dd28317d64d4715d9b36e5a5f0be31f4cfcef25c
parent 109915 07a27119d9c983c88687f406e53ce6cabf4ae028
child 109917 1b3fa4309f3145d736a0a75762d9a1da7382d2c1
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs761393
milestone18.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 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;