tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsFontCache.cpp (8650B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "nsFontCache.h"
      7 
      8 #include "gfxTextRun.h"
      9 #include "mozilla/Services.h"
     10 #include "mozilla/ServoUtils.h"
     11 #include "nsCRT.h"
     12 
     13 #include "mozilla/dom/Document.h"
     14 #include "nsPresContext.h"
     15 
     16 using mozilla::services::GetObserverService;
     17 
     18 NS_IMPL_ISUPPORTS(nsFontCache, nsIObserver)
     19 
     20 static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection");
     21 
     22 // The Init and Destroy methods are necessary because it's not
     23 // safe to call AddObserver from a constructor or RemoveObserver
     24 // from a destructor.  That should be fixed.
     25 void nsFontCache::Init(nsPresContext* aContext) {
     26  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
     27  mContext = aContext;
     28  // register as a memory-pressure observer to free font resources
     29  // in low-memory situations.
     30  nsCOMPtr<nsIObserverService> obs = GetObserverService();
     31  if (obs) {
     32    obs->AddObserver(this, "memory-pressure", false);
     33  }
     34 
     35  mLocaleLanguage = nsLanguageAtomService::GetService()->GetLocaleLanguage();
     36  if (!mLocaleLanguage) {
     37    mLocaleLanguage = NS_Atomize("x-western");
     38  }
     39 }
     40 
     41 void nsFontCache::Destroy() {
     42  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
     43  nsCOMPtr<nsIObserverService> obs = GetObserverService();
     44  if (obs) {
     45    obs->RemoveObserver(this, "memory-pressure");
     46  }
     47  Flush();
     48 }
     49 
     50 NS_IMETHODIMP
     51 nsFontCache::Observe(nsISupports*, const char* aTopic, const char16_t*) {
     52  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
     53  if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
     54    Compact();
     55  }
     56  return NS_OK;
     57 }
     58 
     59 already_AddRefed<nsFontMetrics> nsFontCache::GetMetricsFor(
     60    const nsFont& aFont, const nsFontMetrics::Params& aParams) {
     61  // We may eventually want to put an nsFontCache on canvas2d workers, but for
     62  // now it is only used by the main-thread layout code and stylo.
     63  mozilla::AssertIsMainThreadOrServoFontMetricsLocked();
     64 
     65  nsAtom* language = aParams.language && !aParams.language->IsEmpty()
     66                         ? aParams.language
     67                         : mLocaleLanguage.get();
     68 
     69  // First check our cache
     70  // start from the end, which is where we put the most-recent-used element
     71  const int32_t n = mFontMetrics.Length() - 1;
     72  for (int32_t i = n; i >= 0; --i) {
     73    nsFontMetrics* fm = mFontMetrics.Elements()[i];
     74    if (fm->Font().Equals(aFont) &&
     75        fm->GetUserFontSet() == aParams.userFontSet &&
     76        fm->Language() == language &&
     77        fm->Orientation() == aParams.orientation &&
     78 #ifdef XP_WIN
     79        fm->AllowForceGDIClassic() == aParams.allowForceGDIClassic &&
     80 #endif
     81        fm->ExplicitLanguage() == aParams.explicitLanguage) {
     82      if (i != n) {
     83        // promote it to the end of the cache
     84        mFontMetrics.RemoveElementAtUnsafe(i);
     85        mFontMetrics.AppendElement(fm);
     86      }
     87      fm->GetThebesFontGroup()->UpdateUserFonts();
     88      return do_AddRef(fm);
     89    }
     90  }
     91 
     92  DetectFontFingerprinting(aFont);
     93 
     94  // It's not in the cache. Get font metrics and then cache them.
     95  // If the cache has reached its size limit, drop the older half of the
     96  // entries; but if we're on a stylo thread (the usual case), we have
     97  // to post a task back to the main thread to do the flush.
     98  if (n >= kMaxCacheEntries - 1 && !mFlushPending) {
     99    if (NS_IsMainThread()) {
    100      Flush(mFontMetrics.Length() - kMaxCacheEntries / 2);
    101    } else {
    102      mFlushPending = true;
    103      nsCOMPtr<nsIRunnable> flushTask = new FlushFontMetricsTask(this);
    104      MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(flushTask));
    105    }
    106  }
    107 
    108  nsFontMetrics::Params params = aParams;
    109  params.language = language;
    110  RefPtr<nsFontMetrics> fm = new nsFontMetrics(aFont, params, mContext);
    111  // the mFontMetrics list has the "head" at the end, because append
    112  // is cheaper than insert
    113  mFontMetrics.AppendElement(do_AddRef(fm).take());
    114  return fm.forget();
    115 }
    116 
    117 void nsFontCache::DetectFontFingerprinting(const nsFont& aFont) {
    118  // We try to detect font fingerprinting attempts by recognizing a large
    119  // number of cache misses in a short amount of time, which indicates the
    120  // usage of an unreasonable amount of different fonts by the web page.
    121 
    122  if (aFont.family.families.list.IsEmpty()) {
    123    return;
    124  }
    125 
    126  if (!MOZ_LOG_TEST(gFingerprinterDetection, mozilla::LogLevel::Info) &&
    127      mReportedProbableFingerprinting) {
    128    // We've already reported fingerprinting for this document, and logging
    129    // isn't enabled, so skip the rest of the detection logic.
    130    return;
    131  }
    132 
    133  PRTime now = PR_Now();
    134  nsAutoString key;
    135  for (const auto& family : aFont.family.families.list.AsSpan()) {
    136    if (family.IsGeneric()) {
    137      continue;
    138    }
    139    key.Append(family.AsFamilyName().name.AsAtom()->GetUTF16String());
    140  }
    141  if (key.IsEmpty()) {
    142    return;
    143  }
    144 
    145  auto missedFonts = mMissedFontFamilyNames.Lock();
    146  missedFonts->InsertOrUpdate(key, now);
    147  // Don't bother checking for fingerprinting attempts if we haven't seen
    148  // enough cache misses yet.
    149  if (missedFonts->Count() <= kFingerprintingCacheMissThreshold) {
    150    return;
    151  }
    152  uint16_t fontsMissedRecently = 0;
    153 
    154  bool clearMissedFonts = false;
    155  if (!mReportedProbableFingerprinting) {
    156    for (auto iter = missedFonts->Iter(); !iter.Done(); iter.Next()) {
    157      if (now - kFingerprintingLastNSec <= iter.Data()) {
    158        if (++fontsMissedRecently > kFingerprintingCacheMissThreshold) {
    159          mContext->Document()->RecordFontFingerprinting();
    160          mReportedProbableFingerprinting = true;
    161          clearMissedFonts = true;
    162          break;
    163        }
    164      } else {
    165        // Remove the old entries from missed cache list.
    166        iter.Remove();
    167      }
    168    }
    169    if (mReportedProbableFingerprinting) {
    170      // We detected a font fingerprinter, so print out all the fonts they
    171      // queries and missed
    172      for (auto iter = missedFonts->Iter(); !iter.Done(); iter.Next()) {
    173        MOZ_LOG(
    174            gFingerprinterDetection, mozilla::LogLevel::Info,
    175            ("Font Fingerprinting Tripped | Document %p | Font Family | %s",
    176             mContext->Document(), NS_ConvertUTF16toUTF8(iter.Key()).get()));
    177      }
    178    }
    179  } else {
    180    // I've already reported the font fingerprinting, but I want to report each
    181    // additional font
    182    MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Info,
    183            ("Font Fingerprinting Tripped | Document %p | Font Family | %s",
    184             mContext->Document(), NS_ConvertUTF16toUTF8(key).get()));
    185  }
    186  if (!MOZ_LOG_TEST(gFingerprinterDetection, mozilla::LogLevel::Info) &&
    187      clearMissedFonts) {
    188    missedFonts->Clear();
    189  }
    190 }
    191 
    192 void nsFontCache::UpdateUserFonts(gfxUserFontSet* aUserFontSet) {
    193  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
    194  for (nsFontMetrics* fm : mFontMetrics) {
    195    gfxFontGroup* fg = fm->GetThebesFontGroup();
    196    if (fg->GetUserFontSet() == aUserFontSet) {
    197      fg->UpdateUserFonts();
    198    }
    199  }
    200 }
    201 
    202 void nsFontCache::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) {
    203  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
    204  mFontMetrics.RemoveElement(aFontMetrics);
    205 }
    206 
    207 void nsFontCache::Compact() {
    208  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
    209  // Need to loop backward because the running element can be removed on
    210  // the way
    211  for (int32_t i = mFontMetrics.Length() - 1; i >= 0; --i) {
    212    nsFontMetrics* fm = mFontMetrics[i];
    213    nsFontMetrics* oldfm = fm;
    214    // Destroy() isn't here because we want our device context to be
    215    // notified
    216    NS_RELEASE(fm);  // this will reset fm to nullptr
    217    // if the font is really gone, it would have called back in
    218    // FontMetricsDeleted() and would have removed itself
    219    if (mFontMetrics.IndexOf(oldfm) != mFontMetrics.NoIndex) {
    220      // nope, the font is still there, so let's hold onto it too
    221      NS_ADDREF(oldfm);
    222    }
    223  }
    224  auto missedFonts = mMissedFontFamilyNames.Lock();
    225  missedFonts->Clear();
    226 }
    227 
    228 // Flush the aFlushCount oldest entries, or all if (aFlushCount < 0)
    229 void nsFontCache::Flush(int32_t aFlushCount) {
    230  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
    231  int32_t n = aFlushCount < 0
    232                  ? mFontMetrics.Length()
    233                  : std::min<int32_t>(aFlushCount, mFontMetrics.Length());
    234  for (int32_t i = n - 1; i >= 0; --i) {
    235    nsFontMetrics* fm = mFontMetrics[i];
    236    // Destroy() will unhook our device context from the fm so that we
    237    // won't waste time in triggering the notification of
    238    // FontMetricsDeleted() in the subsequent release
    239    fm->Destroy();
    240    NS_RELEASE(fm);
    241  }
    242  mFontMetrics.RemoveElementsAt(0, n);
    243 }