Bug 1498316 - Limit the number of nsFontMetrics entries cached by each device context, to avoid excessive growth of this cache in examples such as animated font variations or sizes. r=lsalzman
authorJonathan Kew <jkew@mozilla.com>
Sat, 13 Oct 2018 10:03:37 +0100
changeset 489368 b4d64ab443eaf93f046597999253c76e7461953d
parent 489367 54598c1940f1b4cf12c1e122431ce709237c686f
child 489444 27ce807450490a5a443c71b3431d5d51c3223ece
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerslsalzman
bugs1498316
milestone64.0a1
Bug 1498316 - Limit the number of nsFontMetrics entries cached by each device context, to avoid excessive growth of this cache in examples such as animated font variations or sizes. r=lsalzman
gfx/src/nsDeviceContext.cpp
--- a/gfx/src/nsDeviceContext.cpp
+++ b/gfx/src/nsDeviceContext.cpp
@@ -55,26 +55,38 @@ public:
     void Init(nsDeviceContext* aContext);
     void Destroy();
 
     already_AddRefed<nsFontMetrics> GetMetricsFor(
         const nsFont& aFont, const nsFontMetrics::Params& aParams);
 
     void FontMetricsDeleted(const nsFontMetrics* aFontMetrics);
     void Compact();
-    void Flush();
+
+    // Flush aFlushCount oldest entries, or all if aFlushCount is negative
+    void Flush(int32_t aFlushCount = -1);
 
     void UpdateUserFonts(gfxUserFontSet* aUserFontSet);
 
 protected:
+    // If the array of cached entries is about to exceed this threshold,
+    // we'll discard the oldest ones so as to keep the size reasonable.
+    // In practice, the great majority of cache hits are among the last
+    // few entries; keeping thousands of older entries becomes counter-
+    // productive because it can then take too long to scan the cache.
+    static const int32_t kMaxCacheEntries = 128;
+
     ~nsFontCache() {}
 
     nsDeviceContext*          mContext; // owner
     RefPtr<nsAtom>         mLocaleLanguage;
-    nsTArray<nsFontMetrics*>  mFontMetrics;
+
+    // We don't allow this array to grow beyond kMaxCacheEntries,
+    // so use an autoarray to avoid separate allocation.
+    AutoTArray<nsFontMetrics*,kMaxCacheEntries> mFontMetrics;
 };
 
 NS_IMPL_ISUPPORTS(nsFontCache, nsIObserver)
 
 // The Init and Destroy methods are necessary because it's not
 // safe to call AddObserver from a constructor or RemoveObserver
 // from a destructor.  That should be fixed.
 void
@@ -114,18 +126,17 @@ already_AddRefed<nsFontMetrics>
 nsFontCache::GetMetricsFor(const nsFont& aFont,
                            const nsFontMetrics::Params& aParams)
 {
     nsAtom* language = aParams.language ? aParams.language
                                          : mLocaleLanguage.get();
 
     // First check our cache
     // start from the end, which is where we put the most-recent-used element
-
-    int32_t n = mFontMetrics.Length() - 1;
+    const int32_t n = mFontMetrics.Length() - 1;
     for (int32_t i = n; i >= 0; --i) {
         nsFontMetrics* fm = mFontMetrics[i];
         if (fm->Font().Equals(aFont) &&
             fm->GetUserFontSet() == aParams.userFontSet &&
             fm->Language() == language &&
             fm->Orientation() == aParams.orientation) {
             if (i != n) {
                 // promote it to the end of the cache
@@ -133,16 +144,21 @@ nsFontCache::GetMetricsFor(const nsFont&
                 mFontMetrics.AppendElement(fm);
             }
             fm->GetThebesFontGroup()->UpdateUserFonts();
             return do_AddRef(fm);
         }
     }
 
     // It's not in the cache. Get font metrics and then cache them.
+    // If the cache has reached its size limit, drop the older half of the
+    // entries.
+    if (n >= kMaxCacheEntries - 1) {
+        Flush(kMaxCacheEntries / 2);
+    }
 
     nsFontMetrics::Params params = aParams;
     params.language = language;
     RefPtr<nsFontMetrics> fm = new nsFontMetrics(aFont, params, mContext);
     // the mFontMetrics list has the "head" at the end, because append
     // is cheaper than insert
     mFontMetrics.AppendElement(do_AddRef(fm).take());
     return fm.forget();
@@ -180,28 +196,32 @@ nsFontCache::Compact()
         // FontMetricsDeleted() and would have removed itself
         if (mFontMetrics.IndexOf(oldfm) != mFontMetrics.NoIndex) {
             // nope, the font is still there, so let's hold onto it too
             NS_ADDREF(oldfm);
         }
     }
 }
 
+// Flush the aFlushCount oldest entries, or all if (aFlushCount < 0)
 void
-nsFontCache::Flush()
+nsFontCache::Flush(int32_t aFlushCount)
 {
-    for (int32_t i = mFontMetrics.Length()-1; i >= 0; --i) {
+    int32_t n = aFlushCount < 0
+        ? mFontMetrics.Length()
+        : std::min<int32_t>(aFlushCount, mFontMetrics.Length());
+    for (int32_t i = n - 1; i >= 0; --i) {
         nsFontMetrics* fm = mFontMetrics[i];
         // Destroy() will unhook our device context from the fm so that we
         // won't waste time in triggering the notification of
         // FontMetricsDeleted() in the subsequent release
         fm->Destroy();
         NS_RELEASE(fm);
     }
-    mFontMetrics.Clear();
+    mFontMetrics.RemoveElementsAt(0, n);
 }
 
 nsDeviceContext::nsDeviceContext()
     : mWidth(0), mHeight(0),
       mAppUnitsPerDevPixel(-1), mAppUnitsPerDevPixelAtUnitFullZoom(-1),
       mAppUnitsPerPhysicalInch(-1),
       mFullZoom(1.0f), mPrintingScale(1.0f),
       mPrintingTranslate(gfxPoint(0, 0)),