summaryrefslogtreecommitdiffstats
path: root/gfx/src/nsFontCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/src/nsFontCache.cpp178
1 files changed, 178 insertions, 0 deletions
diff --git a/gfx/src/nsFontCache.cpp b/gfx/src/nsFontCache.cpp
new file mode 100644
index 0000000000..856ae3a5c7
--- /dev/null
+++ b/gfx/src/nsFontCache.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFontCache.h"
+
+#include "gfxTextRun.h"
+#include "mozilla/Services.h"
+#include "mozilla/ServoUtils.h"
+#include "nsCRT.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsPresContext.h"
+
+using mozilla::services::GetObserverService;
+
+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 nsFontCache::Init(nsPresContext* aContext) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ mContext = aContext;
+ // register as a memory-pressure observer to free font resources
+ // in low-memory situations.
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "memory-pressure", false);
+ }
+
+ mLocaleLanguage = nsLanguageAtomService::GetService()->GetLocaleLanguage();
+ if (!mLocaleLanguage) {
+ mLocaleLanguage = NS_Atomize("x-western");
+ }
+}
+
+void nsFontCache::Destroy() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "memory-pressure");
+ }
+ Flush();
+}
+
+NS_IMETHODIMP
+nsFontCache::Observe(nsISupports*, const char* aTopic, const char16_t*) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ Compact();
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsFontMetrics> nsFontCache::GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams) {
+ // We may eventually want to put an nsFontCache on canvas2d workers, but for
+ // now it is only used by the main-thread layout code and stylo.
+ mozilla::AssertIsMainThreadOrServoFontMetricsLocked();
+
+ nsAtom* language = aParams.language && !aParams.language->IsEmpty()
+ ? aParams.language
+ : mLocaleLanguage.get();
+
+ // First check our cache
+ // start from the end, which is where we put the most-recent-used element
+ const int32_t n = mFontMetrics.Length() - 1;
+ for (int32_t i = n; i >= 0; --i) {
+ nsFontMetrics* fm = mFontMetrics.Elements()[i];
+ if (fm->Font().Equals(aFont) &&
+ fm->GetUserFontSet() == aParams.userFontSet &&
+ fm->Language() == language &&
+ fm->Orientation() == aParams.orientation &&
+ fm->ExplicitLanguage() == aParams.explicitLanguage) {
+ if (i != n) {
+ // promote it to the end of the cache
+ mFontMetrics.RemoveElementAtUnsafe(i);
+ mFontMetrics.AppendElement(fm);
+ }
+ fm->GetThebesFontGroup()->UpdateUserFonts();
+ return do_AddRef(fm);
+ }
+ }
+
+ if (!mReportedProbableFingerprinting) {
+ // We try to detect font fingerprinting attempts by recognizing a large
+ // number of cache misses in a short amount of time, which indicates the
+ // usage of an unreasonable amount of different fonts by the web page.
+ PRTime now = PR_Now();
+ if (now - mLastCacheMiss > kFingerprintingTimeout) {
+ mCacheMisses = 0;
+ }
+ mCacheMisses++;
+ mLastCacheMiss = now;
+ if (NS_IsMainThread() && mCacheMisses > kFingerprintingCacheMissThreshold) {
+ mContext->Document()->RecordFontFingerprinting();
+ mReportedProbableFingerprinting = true;
+ }
+ }
+
+ // 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; but if we're on a stylo thread (the usual case), we have
+ // to post a task back to the main thread to do the flush.
+ if (n >= kMaxCacheEntries - 1 && !mFlushPending) {
+ if (NS_IsMainThread()) {
+ Flush(mFontMetrics.Length() - kMaxCacheEntries / 2);
+ } else {
+ mFlushPending = true;
+ nsCOMPtr<nsIRunnable> flushTask = new FlushFontMetricsTask(this);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(flushTask));
+ }
+ }
+
+ 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();
+}
+
+void nsFontCache::UpdateUserFonts(gfxUserFontSet* aUserFontSet) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ for (nsFontMetrics* fm : mFontMetrics) {
+ gfxFontGroup* fg = fm->GetThebesFontGroup();
+ if (fg->GetUserFontSet() == aUserFontSet) {
+ fg->UpdateUserFonts();
+ }
+ }
+}
+
+void nsFontCache::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ mFontMetrics.RemoveElement(aFontMetrics);
+}
+
+void nsFontCache::Compact() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ // Need to loop backward because the running element can be removed on
+ // the way
+ for (int32_t i = mFontMetrics.Length() - 1; i >= 0; --i) {
+ nsFontMetrics* fm = mFontMetrics[i];
+ nsFontMetrics* oldfm = fm;
+ // Destroy() isn't here because we want our device context to be
+ // notified
+ NS_RELEASE(fm); // this will reset fm to nullptr
+ // if the font is really gone, it would have called back in
+ // 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(int32_t aFlushCount) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ 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.RemoveElementsAt(0, n);
+
+ mLastCacheMiss = 0;
+ mCacheMisses = 0;
+}