Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/thebes/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 111 kB image not shown  

Quelle  gfxPlatformFontList.cpp

  Sprache: C
 

/* -*- Mode: C++; tab-width: 20; 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 "mozilla/Logging.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/intl/Locale.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/intl/OSPreferences.h"

#include "gfxPlatformFontList.h"
#include "gfxTextRun.h"
#include "gfxUserFontSet.h"
#include "SharedFontList-impl.h"

#include "GeckoProfiler.h"
#include "nsCRT.h"
#include "nsGkAtoms.h"
#include "nsPresContext.h"
#include "nsServiceManagerUtils.h"
#include "nsUnicharUtils.h"
#include "nsUnicodeProperties.h"
#include "nsXULAppAPI.h"

#include "mozilla/AppShutdown.h"
#include "mozilla/Attributes.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/glean/GfxMetrics.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessMessageManager.h"
#include "mozilla/dom/Document.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Unused.h"

#include "base/eintr_wrapper.h"

#include <locale.h>
#include <numeric>

using namespace mozilla;
using mozilla::intl::Locale;
using mozilla::intl::LocaleParser;
using mozilla::intl::LocaleService;
using mozilla::intl::OSPreferences;

#define LOG_FONTLIST(args) \
  MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
#define LOG_FONTLIST_ENABLED() \
  MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
#define LOG_FONTINIT(args) \
  MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
#define LOG_FONTINIT_ENABLED() \
  MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)

gfxPlatformFontList* gfxPlatformFontList::sPlatformFontList = nullptr;

// Character ranges that require complex-script shaping support in the font,
// and so should be masked out by ReadCMAP if the necessary layout tables
// are not present.
// Currently used by the Mac and FT2 implementations only, but probably should
// be supported on Windows as well.
const gfxFontEntry::ScriptRange gfxPlatformFontList::sComplexScriptRanges[] = {
    // Actually, now that harfbuzz supports presentation-forms shaping for
    // Arabic, we can render it without layout tables. So maybe we don't
    // want to mask the basic Arabic block here?
    // This affects the arabic-fallback-*.html reftests, which rely on
    // loading a font that *doesn't* have any GSUB table.
    {0x0600, 0x060B, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    // skip 060C Arabic comma, also used by N'Ko etc
    {0x060D, 0x061A, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    // skip 061B Arabic semicolon, also used by N'Ko etc
    {0x061C, 0x061E, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    // skip 061F Arabic question mark, also used by N'Ko etc
    {0x0620, 0x063F, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    // skip 0640 Arabic tatweel (for syriac, adlam, etc)
    {0x0641, 0x06D3, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    // skip 06D4 Arabic full stop (for hanifi rohingya)
    {0x06D5, 0x06FF, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    {0x0700, 0x074F, 1, {TRUETYPE_TAG('s''y''r''c'), 0, 0}},
    {0x0750, 0x077F, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    {0x08A0, 0x08FF, 1, {TRUETYPE_TAG('a''r''a''b'), 0, 0}},
    {0x0900,
     0x0963,
     2,
     {TRUETYPE_TAG('d''e''v''2'), TRUETYPE_TAG('d''e''v''a'), 0}},
    // skip 0964 DEVANAGARI DANDA and 0965 DEVANAGARI DOUBLE DANDA, shared by
    // various other Indic writing systems
    {0x0966,
     0x097F,
     2,
     {TRUETYPE_TAG('d''e''v''2'), TRUETYPE_TAG('d''e''v''a'), 0}},
    {0x0980,
     0x09FF,
     2,
     {TRUETYPE_TAG('b''n''g''2'), TRUETYPE_TAG('b''e''n''g'), 0}},
    {0x0A00,
     0x0A7F,
     2,
     {TRUETYPE_TAG('g''u''r''2'), TRUETYPE_TAG('g''u''r''u'), 0}},
    {0x0A80,
     0x0AFF,
     2,
     {TRUETYPE_TAG('g''j''r''2'), TRUETYPE_TAG('g''u''j''r'), 0}},
    {0x0B00,
     0x0B7F,
     2,
     {TRUETYPE_TAG('o''r''y''2'), TRUETYPE_TAG('o''r''y''a'), 0}},
    {0x0B80,
     0x0BFF,
     2,
     {TRUETYPE_TAG('t''m''l''2'), TRUETYPE_TAG('t''a''m''l'), 0}},
    {0x0C00,
     0x0C7F,
     2,
     {TRUETYPE_TAG('t''e''l''2'), TRUETYPE_TAG('t''e''l''u'), 0}},
    {0x0C80,
     0x0CFF,
     2,
     {TRUETYPE_TAG('k''n''d''2'), TRUETYPE_TAG('k''n''d''a'), 0}},
    {0x0D00,
     0x0D7F,
     2,
     {TRUETYPE_TAG('m''l''m''2'), TRUETYPE_TAG('m''l''y''m'), 0}},
    {0x0D80, 0x0DFF, 1, {TRUETYPE_TAG('s''i''n''h'), 0, 0}},
    {0x0E80, 0x0EFF, 1, {TRUETYPE_TAG('l''a''o'' '), 0, 0}},
    {0x0F00, 0x0FFF, 1, {TRUETYPE_TAG('t''i''b''t'), 0, 0}},
    {0x1000,
     0x109f,
     2,
     {TRUETYPE_TAG('m''y''m''r'), TRUETYPE_TAG('m''y''m''2'), 0}},
    {0x1780, 0x17ff, 1, {TRUETYPE_TAG('k''h''m''r'), 0, 0}},
    // Khmer Symbols (19e0..19ff) don't seem to need any special shaping
    {0xaa60,
     0xaa7f,
     2,
     {TRUETYPE_TAG('m''y''m''r'), TRUETYPE_TAG('m''y''m''2'), 0}},
    // Thai seems to be "renderable" without AAT morphing tables
    {0, 0, 0, {0, 0, 0}}  // terminator
};

static const char* kObservedPrefs[] = {
    "font.""font.name-list.""intl.accept_languages",  // hmmmm...
    "browser.display.use_document_fonts.icon_font_allowlist", nullptr};

static const char kFontSystemWhitelistPref[] = "font.system.whitelist";

static const char kCJKFallbackOrderPref[] = "font.cjk_pref_fallback_order";

// Pref for the list of icon font families that still get to override the
// default font from prefs, even when use_document_fonts is disabled.
// (This is to enable ligature-based icon fonts to keep working.)
static const char kIconFontsPref[] =
    "browser.display.use_document_fonts.icon_font_allowlist";

// xxx - this can probably be eliminated by reworking pref font handling code
static const char* gPrefLangNames[] = {
#define FONT_PREF_LANG(enum_id_, str_, atom_id_) str_
#include "gfxFontPrefLangList.h"
#undef FONT_PREF_LANG
};

static_assert(std::size(gPrefLangNames) == uint32_t(eFontPrefLang_Count),
              "size of pref lang name array doesn't match pref lang enum size");

class gfxFontListPrefObserver final : public nsIObserver {
  ~gfxFontListPrefObserver() = default;

 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
};

static void FontListPrefChanged(const char* aPref, void* aData = nullptr) {
  // XXX this could be made to only clear out the cache for the prefs that were
  // changed but it probably isn't that big a deal.
  gfxPlatformFontList::PlatformFontList()->ClearLangGroupPrefFonts();
  gfxPlatformFontList::PlatformFontList()->LoadIconFontOverrideList();
  gfxFontCache::GetCache()->Flush();
}

static gfxFontListPrefObserver* gFontListPrefObserver = nullptr;

NS_IMPL_ISUPPORTS(gfxFontListPrefObserver, nsIObserver)

#define LOCALES_CHANGED_TOPIC "intl:system-locales-changed"

NS_IMETHODIMP
gfxFontListPrefObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                 const char16_t* aData) {
  NS_ASSERTION(!strcmp(aTopic, LOCALES_CHANGED_TOPIC), "invalid topic");
  FontListPrefChanged(nullptr);

  if (XRE_IsParentProcess()) {
    gfxPlatform::GlobalReflowFlags flags =
        gfxPlatform::GlobalReflowFlags::BroadcastToChildren |
        gfxPlatform::GlobalReflowFlags::FontsChanged;
    gfxPlatform::ForceGlobalReflow(flags);
  }
  return NS_OK;
}

MOZ_DEFINE_MALLOC_SIZE_OF(FontListMallocSizeOf)

NS_IMPL_ISUPPORTS(gfxPlatformFontList::MemoryReporter, nsIMemoryReporter)

NS_IMETHODIMP
gfxPlatformFontList::MemoryReporter::CollectReports(
    nsIHandleReportCallback* aHandleReport, nsISupports* aData,
    bool aAnonymize) {
  FontListSizes sizes;
  sizes.mFontListSize = 0;
  sizes.mFontTableCacheSize = 0;
  sizes.mCharMapsSize = 0;
  sizes.mLoaderSize = 0;
  sizes.mSharedSize = 0;

  gfxPlatformFontList::PlatformFontList()->AddSizeOfIncludingThis(
      &FontListMallocSizeOf, &sizes);

  MOZ_COLLECT_REPORT(
      "explicit/gfx/font-list", KIND_HEAP, UNITS_BYTES, sizes.mFontListSize,
      "Memory used to manage the list of font families and faces.");

  MOZ_COLLECT_REPORT(
      "explicit/gfx/font-charmaps", KIND_HEAP, UNITS_BYTES, sizes.mCharMapsSize,
      "Memory used to record the character coverage of individual fonts.");

  if (sizes.mFontTableCacheSize) {
    MOZ_COLLECT_REPORT(
        "explicit/gfx/font-tables", KIND_HEAP, UNITS_BYTES,
        sizes.mFontTableCacheSize,
        "Memory used for cached font metrics and layout tables.");
  }

  if (sizes.mLoaderSize) {
    MOZ_COLLECT_REPORT("explicit/gfx/font-loader", KIND_HEAP, UNITS_BYTES,
                       sizes.mLoaderSize,
                       "Memory used for (platform-specific) font loader.");
  }

  if (sizes.mSharedSize) {
    MOZ_COLLECT_REPORT(
        "font-list-shmem", KIND_NONHEAP, UNITS_BYTES, sizes.mSharedSize,
        "Shared memory for system font list and character coverage data.");
  }

  return NS_OK;
}

PRThread* gfxPlatformFontList::sInitFontListThread = nullptr;

static void InitFontListCallback(void* aFontList) {
  AUTO_PROFILER_REGISTER_THREAD("InitFontList");
  PR_SetCurrentThreadName("InitFontList");

  if (!static_cast<gfxPlatformFontList*>(aFontList)->InitFontList()) {
    gfxPlatformFontList::Shutdown();
  }
}

/* static */
bool gfxPlatformFontList::Initialize(gfxPlatformFontList* aList) {
  sPlatformFontList = aList;
  if (XRE_IsParentProcess() &&
      StaticPrefs::gfx_font_list_omt_enabled_AtStartup() &&
      StaticPrefs::gfx_e10s_font_list_shared_AtStartup() &&
      !gfxPlatform::InSafeMode()) {
    sInitFontListThread = PR_CreateThread(
        PR_USER_THREAD, InitFontListCallback, aList, PR_PRIORITY_NORMAL,
        PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
    return true;
  }
  if (aList->InitFontList()) {
    return true;
  }
  Shutdown();
  return false;
}

gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames)
    : mLock("gfxPlatformFontList lock"),
      mFontFamilies(64),
      mOtherFamilyNames(16),
      mSharedCmaps(8) {
  if (aNeedFullnamePostscriptNames) {
    mExtraNames = MakeUnique<ExtraNames>();
  }

  mLangService = nsLanguageAtomService::GetService();

  LoadBadUnderlineList();
  LoadIconFontOverrideList();

  mFontPrefs = MakeUnique<FontPrefs>();

  gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, mEnabledFontsList);
  mFontFamilyWhitelistActive = !mEnabledFontsList.IsEmpty();

  // pref changes notification setup
  NS_ASSERTION(!gFontListPrefObserver,
               "There has been font list pref observer already");
  gFontListPrefObserver = new gfxFontListPrefObserver();
  NS_ADDREF(gFontListPrefObserver);

  Preferences::RegisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs);

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    obs->AddObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC, false);
  }

  // Only the parent process listens for whitelist changes; it will then
  // notify its children to rebuild their font lists.
  if (XRE_IsParentProcess()) {
    Preferences::RegisterCallback(FontWhitelistPrefChanged,
                                  kFontSystemWhitelistPref);
  }

  RegisterStrongMemoryReporter(new MemoryReporter());

  // initialize lang group pref font defaults (i.e. serif/sans-serif)
  mDefaultGenericsLangGroup.AppendElements(std::size(gPrefLangNames));
  for (uint32_t i = 0; i < std::size(gPrefLangNames); i++) {
    nsAutoCString prefDefaultFontType("font.default.");
    prefDefaultFontType.Append(GetPrefLangName(eFontPrefLang(i)));
    nsAutoCString serifOrSans;
    Preferences::GetCString(prefDefaultFontType.get(), serifOrSans);
    if (serifOrSans.EqualsLiteral("sans-serif")) {
      mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::SansSerif;
    } else {
      mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::Serif;
    }
  }
}

gfxPlatformFontList::~gfxPlatformFontList() {
  // Note that gfxPlatformFontList::Shutdown() ensures that the init-font-list
  // thread is finished before we come here.

  AutoLock lock(mLock);

  // We can't just do mSharedCmaps.Clear() here because removing each item from
  // the table would drop its last reference, and its Release() method would
  // then call back to MaybeRemoveCmap to search for it, which we can't do
  // while in the middle of clearing the table.
  // So we first clear the "shared" flag in each entry, so Release() won't try
  // to re-find them in the table.
  for (auto iter = mSharedCmaps.ConstIter(); !iter.Done(); iter.Next()) {
    iter.Get()->mCharMap->ClearSharedFlag();
  }
  mSharedCmaps.Clear();

  ClearLangGroupPrefFontsLocked();

  NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer");

  Preferences::UnregisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs);

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    obs->RemoveObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC);
  }

  if (XRE_IsParentProcess()) {
    Preferences::UnregisterCallback(FontWhitelistPrefChanged,
                                    kFontSystemWhitelistPref);
  }
  NS_RELEASE(gFontListPrefObserver);
}

void gfxPlatformFontList::GetMissingFonts(nsTArray<nsCString>& aMissingFonts) {
  AutoLock lock(mLock);

  auto fontLists = GetFilteredPlatformFontLists();

  if (!fontLists.Length()) {
    return;
  }

  for (unsigned int i = 0; i < fontLists.Length(); i++) {
    for (unsigned int j = 0; j < fontLists[i].second; j++) {
      nsCString key(fontLists[i].first[j]);
      GenerateFontListKey(key);

      if (SharedFontList()) {
        fontlist::Family* family = SharedFontList()->FindFamily(key);
        if (!family) {
          aMissingFonts.AppendElement(fontLists[i].first[j]);
        }
      } else {
        gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
        if (!familyEntry) {
          familyEntry = mOtherFamilyNames.GetWeak(key);
        }
        if (!familyEntry) {
          aMissingFonts.AppendElement(fontLists[i].first[j]);
        }
      }
    }
  }
}

void gfxPlatformFontList::GetMissingFonts(nsCString& aMissingFonts) {
  nsTArray<nsCString> fontList;
  GetMissingFonts(fontList);

  if (fontList.IsEmpty()) {
    aMissingFonts.Append("No font list available for this device.");
    return;
  }

  fontList.Sort();
  aMissingFonts.Append(StringJoin("|"_ns, fontList));
}

/* static */
void gfxPlatformFontList::FontWhitelistPrefChanged(const char* aPref,
                                                   void* aClosure) {
  MOZ_ASSERT(XRE_IsParentProcess());
  auto* pfl = gfxPlatformFontList::PlatformFontList();
  pfl->UpdateFontList(true);
  dom::ContentParent::NotifyUpdatedFonts(true);
}

void gfxPlatformFontList::ApplyWhitelist() {
  uint32_t numFonts = mEnabledFontsList.Length();
  if (!mFontFamilyWhitelistActive) {
    return;
  }
  nsTHashSet<nsCString> familyNamesWhitelist;
  for (uint32_t i = 0; i < numFonts; i++) {
    nsAutoCString key;
    ToLowerCase(mEnabledFontsList[i], key);
    familyNamesWhitelist.Insert(key);
  }
  AutoTArray<RefPtr<gfxFontFamily>, 128> accepted;
  bool whitelistedFontFound = false;
  for (const auto& entry : mFontFamilies) {
    nsAutoCString fontFamilyName(entry.GetKey());
    ToLowerCase(fontFamilyName);
    if (familyNamesWhitelist.Contains(fontFamilyName)) {
      accepted.AppendElement(entry.GetData());
      whitelistedFontFound = true;
    }
  }
  if (!whitelistedFontFound) {
    // No whitelisted fonts found! Ignore the whitelist.
    return;
  }
  // Replace the original full list with the accepted subset.
  mFontFamilies.Clear();
  for (auto& f : accepted) {
    nsAutoCString fontFamilyName(f->Name());
    ToLowerCase(fontFamilyName);
    mFontFamilies.InsertOrUpdate(fontFamilyName, std::move(f));
  }
}

void gfxPlatformFontList::ApplyWhitelist(
    nsTArray<fontlist::Family::InitData>& aFamilies) {
  mLock.AssertCurrentThreadIn();
  if (!mFontFamilyWhitelistActive) {
    return;
  }
  nsTHashSet<nsCString> familyNamesWhitelist;
  for (const auto& item : mEnabledFontsList) {
    nsAutoCString key;
    ToLowerCase(item, key);
    familyNamesWhitelist.Insert(key);
  }
  AutoTArray<fontlist::Family::InitData, 128> accepted;
  bool keptNonHidden = false;
  for (auto& f : aFamilies) {
    if (familyNamesWhitelist.Contains(f.mKey)) {
      accepted.AppendElement(f);
      if (f.mVisibility != FontVisibility::Hidden) {
        keptNonHidden = true;
      }
    }
  }
  if (!keptNonHidden) {
    // No (visible) families were whitelisted: ignore the whitelist
    // and just leave the fontlist unchanged.
    return;
  }
  aFamilies = std::move(accepted);
}

bool gfxPlatformFontList::FamilyInList(const nsACString& aName,
                                       const char* aList[], size_t aCount) {
  size_t result;
  return BinarySearchIf(
      aList, 0, aCount,
      [&](const charconst aVal) -> int {
        return nsCaseInsensitiveUTF8StringComparator(
            aName.BeginReading(), aVal, aName.Length(), strlen(aVal));
      },
      &result);
}

void gfxPlatformFontList::CheckFamilyList(const char* aList[], size_t aCount) {
#ifdef DEBUG
  MOZ_ASSERT(aCount > 0, "empty font family list?");
  const char* a = aList[0];
  uint32_t aLen = strlen(a);
  for (size_t i = 1; i < aCount; ++i) {
    const char* b = aList[i];
    uint32_t bLen = strlen(b);
    if (nsCaseInsensitiveUTF8StringComparator(a, b, aLen, bLen) >= 0) {
      MOZ_CRASH_UNSAFE_PRINTF("incorrectly sorted font family list: %s >= %s",
                              a, b);
    }
    a = b;
    aLen = bLen;
  }
#endif
}

bool gfxPlatformFontList::AddWithLegacyFamilyName(const nsACString& aLegacyName,
                                                  gfxFontEntry* aFontEntry,
                                                  FontVisibility aVisibility) {
  mLock.AssertCurrentThreadIn();
  bool added = false;
  nsAutoCString key;
  ToLowerCase(aLegacyName, key);
  mOtherFamilyNames
      .LookupOrInsertWith(key,
                          [&] {
                            RefPtr<gfxFontFamily> family =
                                CreateFontFamily(aLegacyName, aVisibility);
                            // We don't want the family to search for faces,
                            // we're adding them directly here.
                            family->SetHasStyles(true);
                            // And we don't want it to attempt to search for
                            // legacy names, because we've already done that
                            // (and this is the result).
                            family->SetCheckedForLegacyFamilyNames(true);
                            added = true;
                            return family;
                          })
      ->AddFontEntry(aFontEntry->Clone());
  return added;
}

bool gfxPlatformFontList::InitFontList() {
  // If the startup font-list-init thread is still running, we need to wait
  // for it to finish before trying to reinitialize here.
  if (sInitFontListThread && !IsInitFontListThread()) {
    PR_JoinThread(sInitFontListThread);
    sInitFontListThread = nullptr;
  }

  AutoLock lock(mLock);

  if (LOG_FONTINIT_ENABLED()) {
    LOG_FONTINIT(("(fontinit) system fontlist initialization\n"));
  }

  if (IsInitialized()) {
    // Font-list reinitialization always occurs on the main thread, in response
    // to a change notification; it's only the initial creation during startup
    // that may be on another thread.
    MOZ_ASSERT(NS_IsMainThread());

    // Rebuilding fontlist so clear out font/word caches.
    gfxFontCache* fontCache = gfxFontCache::GetCache();
    if (fontCache) {
      fontCache->FlushShapedWordCaches();
      fontCache->Flush();
    }

    gfxPlatform::PurgeSkiaFontCache();

    // There's no need to broadcast this reflow request to child processes, as
    // ContentParent::NotifyUpdatedFonts deals with it by re-entering into this
    // function on child processes.
    gfxPlatform::GlobalReflowFlags flags =
        gfxPlatform::GlobalReflowFlags::NeedsReframe |
        gfxPlatform::GlobalReflowFlags::FontsChanged;
    ForceGlobalReflowLocked(flags);

    mAliasTable.Clear();
    mLocalNameTable.Clear();
    mIconFontsSet.Clear();

    CancelLoadCmapsTask();
    mStartedLoadingCmapsFrom = 0xffffffffu;

    CancelInitOtherFamilyNamesTask();
    mFontFamilies.Clear();
    mOtherFamilyNames.Clear();
    mOtherFamilyNamesInitialized = false;

    if (mExtraNames) {
      mExtraNames->mFullnames.Clear();
      mExtraNames->mPostscriptNames.Clear();
    }
    mFaceNameListsInitialized = false;
    ClearLangGroupPrefFontsLocked();
    CancelLoader();

    // Clear cached family records that will no longer be valid.
    for (auto& f : mReplacementCharFallbackFamily) {
      f = FontFamily();
    }

    gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, mEnabledFontsList);
    mFontFamilyWhitelistActive = !mEnabledFontsList.IsEmpty();

    LoadIconFontOverrideList();
  }

  // From here, gfxPlatformFontList::IsInitialized will return true,
  // unless InitFontListForPlatform() fails and we reset it below.
  mFontlistInitCount++;

  InitializeCodepointsWithNoFonts();

  // Try to initialize the cross-process shared font list if enabled by prefs,
  // but not if we're running in Safe Mode.
  if (StaticPrefs::gfx_e10s_font_list_shared_AtStartup() &&
      !gfxPlatform::InSafeMode()) {
    for (const auto& entry : mFontEntries.Values()) {
      if (!entry) {
        continue;
      }
      AutoWriteLock lock(entry->mLock);
      entry->mShmemCharacterMap = nullptr;
      entry->mShmemFace = nullptr;
      entry->mFamilyName.Truncate();
    }
    mFontEntries.Clear();
    mShmemCharMaps.Clear();
    bool oldSharedList = mSharedFontList != nullptr;
    mSharedFontList.reset(new fontlist::FontList(mFontlistInitCount));
    InitSharedFontListForPlatform();
    if (mSharedFontList && mSharedFontList->Initialized()) {
      if (mLocalNameTable.Count()) {
        SharedFontList()->SetLocalNames(mLocalNameTable);
        mLocalNameTable.Clear();
      }
    } else {
      // something went wrong, fall back to in-process list
      gfxCriticalNote << "Failed to initialize shared font list, "
                         "falling back to in-process list.";
      mSharedFontList.reset(nullptr);
    }
    if (oldSharedList && XRE_IsParentProcess()) {
      // notify all children of the change
      if (NS_IsMainThread()) {
        dom::ContentParent::NotifyUpdatedFonts(true);
      } else {
        NS_DispatchToMainThread(NS_NewRunnableFunction(
            "NotifyUpdatedFonts callback",
            [] { dom::ContentParent::NotifyUpdatedFonts(true); }));
      }
    }
  }

  if (!SharedFontList()) {
    if (NS_FAILED(InitFontListForPlatform())) {
      mFontlistInitCount = 0;
      return false;
    }
    ApplyWhitelist();
  }

  // Set up mDefaultFontEntry as a "last resort" default that we can use
  // to avoid crashing if the font list is otherwise unusable.
  gfxFontStyle defStyle;
  FontFamily fam = GetDefaultFontLocked(nullptr, &defStyle);
  gfxFontEntry* fe;
  if (fam.mShared) {
    auto face = fam.mShared->FindFaceForStyle(SharedFontList(), defStyle);
    fe = face ? GetOrCreateFontEntryLocked(face, fam.mShared) : nullptr;
  } else {
    fe = fam.mUnshared->FindFontForStyle(defStyle);
  }
  mDefaultFontEntry = fe;

  return true;
}

void gfxPlatformFontList::LoadIconFontOverrideList() {
  mIconFontsSet.Clear();
  AutoTArray<nsCString, 20> iconFontsList;
  gfxFontUtils::GetPrefsFontList(kIconFontsPref, iconFontsList);
  for (auto& name : iconFontsList) {
    ToLowerCase(name);
    mIconFontsSet.Insert(name);
  }
}

void gfxPlatformFontList::InitializeCodepointsWithNoFonts() {
  auto& first = mCodepointsWithNoFonts[FontVisibility(0)];
  for (auto& bitset : mCodepointsWithNoFonts) {
    if (&bitset == &first) {
      bitset.reset();
      bitset.SetRange(0, 0x1f);            // C0 controls
      bitset.SetRange(0x7f, 0x9f);         // C1 controls
      bitset.SetRange(0xE000, 0xF8FF);     // PUA
      bitset.SetRange(0xF0000, 0x10FFFD);  // Supplementary PUA
      bitset.SetRange(0xfdd0, 0xfdef);     // noncharacters
      for (unsigned i = 0; i <= 0x100000; i += 0x10000) {
        bitset.SetRange(i + 0xfffe, i + 0xffff);  // noncharacters
      }
      bitset.Compact();
    } else {
      bitset = first;
    }
  }
}

void gfxPlatformFontList::GenerateFontListKey(const nsACString& aKeyName,
                                              nsACString& aResult) {
  aResult = aKeyName;
  ToLowerCase(aResult);
}

void gfxPlatformFontList::GenerateFontListKey(nsACString& aKeyName) {
  ToLowerCase(aKeyName);
}

// Used if a stylo thread wants to trigger InitOtherFamilyNames in the main
// process: we can't do IPC from the stylo thread so we post this to the main
// thread instead.
class InitOtherFamilyNamesForStylo : public mozilla::Runnable {
 public:
  explicit InitOtherFamilyNamesForStylo(bool aDeferOtherFamilyNamesLoading)
      : Runnable("gfxPlatformFontList::InitOtherFamilyNamesForStylo"),
        mDefer(aDeferOtherFamilyNamesLoading) {}

  NS_IMETHOD Run() override {
    auto pfl = gfxPlatformFontList::PlatformFontList();
    auto list = pfl->SharedFontList();
    if (!list) {
      return NS_OK;
    }
    bool initialized = false;
    dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames(
        list->GetGeneration(), mDefer, &initialized);
    pfl->mOtherFamilyNamesInitialized.compareExchange(false, initialized);
    return NS_OK;
  }

 private:
  bool mDefer;
};

#define OTHERNAMES_TIMEOUT 200

bool gfxPlatformFontList::InitOtherFamilyNames(
    bool aDeferOtherFamilyNamesLoading) {
  if (mOtherFamilyNamesInitialized) {
    return true;
  }

  if (SharedFontList() && !XRE_IsParentProcess()) {
    if (NS_IsMainThread()) {
      bool initialized;
      dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames(
          SharedFontList()->GetGeneration(), aDeferOtherFamilyNamesLoading,
          &initialized);
      mOtherFamilyNamesInitialized.compareExchange(false, initialized);
    } else {
      NS_DispatchToMainThread(
          new InitOtherFamilyNamesForStylo(aDeferOtherFamilyNamesLoading));
    }
    return mOtherFamilyNamesInitialized;
  }

  // If the font loader delay has been set to zero, we don't defer loading
  // additional family names (regardless of the aDefer... parameter), as we
  // take this to mean availability of font info is to be prioritized over
  // potential startup perf or main-thread jank.
  // (This is used so we can reliably run reftests that depend on localized
  // font-family names being available.)
  if (aDeferOtherFamilyNamesLoading &&
      StaticPrefs::gfx_font_loader_delay() > 0) {
    if (!mPendingOtherFamilyNameTask) {
      RefPtr<mozilla::CancelableRunnable> task =
          new InitOtherFamilyNamesRunnable();
      mPendingOtherFamilyNameTask = task;
      NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle);
    }
  } else {
    InitOtherFamilyNamesInternal(false);
  }
  return mOtherFamilyNamesInitialized;
}

// time limit for loading facename lists (ms)
#define NAMELIST_TIMEOUT 200

gfxFontEntry* gfxPlatformFontList::SearchFamiliesForFaceName(
    const nsACString& aFaceName) {
  TimeStamp start = TimeStamp::Now();
  bool timedOut = false;
  // if mFirstChar is not 0, only load facenames for families
  // that start with this character
  char16_t firstChar = 0;
  gfxFontEntry* lookup = nullptr;

  // iterate over familes starting with the same letter
  firstChar = ToLowerCase(aFaceName.CharAt(0));

  for (const auto& entry : mFontFamilies) {
    nsCStringHashKey::KeyType key = entry.GetKey();
    const RefPtr<gfxFontFamily>& family = entry.GetData();

    // when filtering, skip names that don't start with the filter character
    if (firstChar && ToLowerCase(key.CharAt(0)) != firstChar) {
      continue;
    }

    family->ReadFaceNames(this, NeedFullnamePostscriptNames());

    TimeDuration elapsed = TimeStamp::Now() - start;
    if (elapsed.ToMilliseconds() > NAMELIST_TIMEOUT) {
      timedOut = true;
      break;
    }
  }

  lookup = FindFaceName(aFaceName);

  TimeDuration elapsed = TimeStamp::Now() - start;
  glean::fontlist::initfacenamelists.AccumulateRawDuration(elapsed);
  if (LOG_FONTINIT_ENABLED()) {
    LOG_FONTINIT(("(fontinit) SearchFamiliesForFaceName took %8.2f ms %s %s",
                  elapsed.ToMilliseconds(), (lookup ? "found name" : ""),
                  (timedOut ? "timeout" : "")));
  }

  return lookup;
}

gfxFontEntry* gfxPlatformFontList::FindFaceName(const nsACString& aFaceName) {
  gfxFontEntry* lookup;

  // lookup in name lookup tables, return null if not found
  if (mExtraNames &&
      ((lookup = mExtraNames->mPostscriptNames.GetWeak(aFaceName)) ||
       (lookup = mExtraNames->mFullnames.GetWeak(aFaceName)))) {
    return lookup;
  }

  return nullptr;
}

gfxFontEntry* gfxPlatformFontList::LookupInFaceNameLists(
    const nsACString& aFaceName) {
  gfxFontEntry* lookup = nullptr;

  // initialize facename lookup tables if needed
  // note: this can terminate early or time out, in which case
  //       mFaceNameListsInitialized remains false
  if (!mFaceNameListsInitialized) {
    lookup = SearchFamiliesForFaceName(aFaceName);
    if (lookup) {
      return lookup;
    }
  }

  // lookup in name lookup tables, return null if not found
  if (!(lookup = FindFaceName(aFaceName))) {
    // names not completely initialized, so keep track of lookup misses
    if (!mFaceNameListsInitialized) {
      if (!mFaceNamesMissed) {
        mFaceNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2);
      }
      mFaceNamesMissed->Insert(aFaceName);
    }
  }

  return lookup;
}

gfxFontEntry* gfxPlatformFontList::LookupInSharedFaceNameList(
    nsPresContext* aPresContext, const nsACString& aFaceName,
    WeightRange aWeightForEntry, StretchRange aStretchForEntry,
    SlantStyleRange aStyleForEntry) {
  nsAutoCString keyName(aFaceName);
  ToLowerCase(keyName);
  fontlist::FontList* list = SharedFontList();
  fontlist::Family* family = nullptr;
  fontlist::Face* face = nullptr;
  if (list->NumLocalFaces()) {
    fontlist::LocalFaceRec* rec = list->FindLocalFace(keyName);
    if (rec) {
      auto* families = list->Families();
      if (families) {
        family = &families[rec->mFamilyIndex];
        face = family->Faces(list)[rec->mFaceIndex].ToPtr<fontlist::Face>(list);
      }
    }
  } else {
    list->SearchForLocalFace(keyName, &family, &face);
  }
  if (!face || !family) {
    return nullptr;
  }
  FontVisibility level =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
  if (!IsVisibleToCSS(*family, level)) {
    if (aPresContext) {
      aPresContext->ReportBlockedFontFamily(*family);
    }
    return nullptr;
  }
  gfxFontEntry* fe = CreateFontEntry(face, family);
  if (fe) {
    fe->mIsLocalUserFont = true;
    fe->mWeightRange = aWeightForEntry;
    fe->mStretchRange = aStretchForEntry;
    fe->mStyleRange = aStyleForEntry;
  }
  return fe;
}

void gfxPlatformFontList::MaybeAddToLocalNameTable(
    const nsACString& aName, const fontlist::LocalFaceRec::InitData& aData) {
  // Compute a measure of the similarity between aName (which will be a PSName
  // or FullName) and aReference (a font family name).
  auto nameSimilarity = [](const nsACString& aName,
                           const nsACString& aReference) -> uint32_t {
    uint32_t nameIdx = 0, refIdx = 0, matchCount = 0;
    while (nameIdx < aName.Length() && refIdx < aReference.Length()) {
      // Ignore non-alphanumerics in the ASCII range, so that a PSname like
      // "TimesNewRomanPSMT" is a good match for family "Times New Roman".
      while (nameIdx < aName.Length() && IsAscii(aName[nameIdx]) &&
             !IsAsciiAlphanumeric(aName[nameIdx])) {
        ++nameIdx;
      }
      while (refIdx < aReference.Length() && IsAscii(aReference[refIdx]) &&
             !IsAsciiAlphanumeric(aReference[refIdx])) {
        ++refIdx;
      }
      if (nameIdx == aName.Length() || refIdx == aReference.Length() ||
          aName[nameIdx] != aReference[refIdx]) {
        break;
      }
      ++nameIdx;
      ++refIdx;
      ++matchCount;
    }
    return matchCount;
  };

  mLocalNameTable.WithEntryHandle(aName, [&](auto entry) -> void {
    if (entry) {
      if (nameSimilarity(aName, aData.mFamilyName) >
          nameSimilarity(aName, entry.Data().mFamilyName)) {
        entry.Update(aData);
      }
    } else {
      entry.OrInsert(aData);
    }
  });
}

void gfxPlatformFontList::LoadBadUnderlineList() {
  gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset",
                                 mBadUnderlineFamilyNames);
  for (auto& fam : mBadUnderlineFamilyNames) {
    ToLowerCase(fam);
  }
  mBadUnderlineFamilyNames.Compact();
  mBadUnderlineFamilyNames.Sort();
}

void gfxPlatformFontList::UpdateFontList(bool aFullRebuild) {
  MOZ_ASSERT(NS_IsMainThread());
  if (aFullRebuild) {
    InitFontList();
    AutoLock lock(mLock);
    RebuildLocalFonts();
  } else {
    // The font list isn't being fully rebuilt, we're just being notified that
    // character maps have been updated and so font fallback needs to be re-
    // done. We only care about this if we have previously encountered a
    // fallback that required cmaps that were not yet available, and so we
    // asked for the async cmap loader to run.
    AutoLock lock(mLock);
    if (mStartedLoadingCmapsFrom != 0xffffffffu) {
      InitializeCodepointsWithNoFonts();
      mStartedLoadingCmapsFrom = 0xffffffffu;
      gfxPlatform::GlobalReflowFlags flags =
          gfxPlatform::GlobalReflowFlags::FontsChanged |
          gfxPlatform::GlobalReflowFlags::BroadcastToChildren;
      ForceGlobalReflowLocked(flags);
    }
  }
}

bool gfxPlatformFontList::IsVisibleToCSS(const gfxFontFamily& aFamily,
                                         FontVisibility aVisibility) const {
  return aFamily.Visibility() <= aVisibility || IsFontFamilyWhitelistActive();
}

bool gfxPlatformFontList::IsVisibleToCSS(const fontlist::Family& aFamily,
                                         FontVisibility aVisibility) const {
  return aFamily.Visibility() <= aVisibility || IsFontFamilyWhitelistActive();
}

void gfxPlatformFontList::GetFontList(nsAtom* aLangGroup,
                                      const nsACString& aGenericFamily,
                                      nsTArray<nsString>& aListOfFonts) {
  AutoLock lock(mLock);

  if (SharedFontList()) {
    fontlist::FontList* list = SharedFontList();
    const fontlist::Family* families = list->Families();
    if (families) {
      for (uint32_t i = 0; i < list->NumFamilies(); i++) {
        auto& f = families[i];
        if (!IsVisibleToCSS(f, FontVisibility::User) || f.IsAltLocaleFamily()) {
          continue;
        }
        // XXX TODO: filter families for aGenericFamily, if supported by
        // platform
        aListOfFonts.AppendElement(
            NS_ConvertUTF8toUTF16(list->LocalizedFamilyName(&f)));
      }
    }
    return;
  }

  for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
    if (!IsVisibleToCSS(*family, FontVisibility::User)) {
      continue;
    }
    if (family->FilterForFontList(aLangGroup, aGenericFamily)) {
      nsAutoCString localizedFamilyName;
      family->LocalizedName(localizedFamilyName);
      aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(localizedFamilyName));
    }
  }

  aListOfFonts.Sort();
  aListOfFonts.Compact();
}

void gfxPlatformFontList::GetFontFamilyList(
    nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray) {
  AutoLock lock(mLock);
  MOZ_ASSERT(aFamilyArray.IsEmpty());
  // This doesn't use ToArray, because the caller passes an AutoTArray.
  aFamilyArray.SetCapacity(mFontFamilies.Count());
  for (const auto& family : mFontFamilies.Values()) {
    aFamilyArray.AppendElement(family);
  }
}

already_AddRefed<gfxFont> gfxPlatformFontList::SystemFindFontForChar(
    nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
    Script aRunScript, eFontPresentation aPresentation,
    const gfxFontStyle* aStyle, FontVisibility* aVisibility) {
  AutoLock lock(mLock);
  FontVisibility level =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
  MOZ_ASSERT(!mCodepointsWithNoFonts[level].test(aCh),
             "don't call for codepoints already known to be unsupported");

  // Try to short-circuit font fallback for U+FFFD, used to represent
  // encoding errors: just use cached family from last time U+FFFD was seen.
  // This helps speed up pages with lots of encoding errors, binary-as-text,
  // etc.
  if (aCh == 0xFFFD) {
    gfxFontEntry* fontEntry = nullptr;
    auto& fallbackFamily = mReplacementCharFallbackFamily[level];
    if (fallbackFamily.mShared) {
      fontlist::Face* face =
          fallbackFamily.mShared->FindFaceForStyle(SharedFontList(), *aStyle);
      if (face) {
        fontEntry = GetOrCreateFontEntryLocked(face, fallbackFamily.mShared);
        *aVisibility = fallbackFamily.mShared->Visibility();
      }
    } else if (fallbackFamily.mUnshared) {
      fontEntry = fallbackFamily.mUnshared->FindFontForStyle(*aStyle);
      *aVisibility = fallbackFamily.mUnshared->Visibility();
    }

    // this should never fail, as we must have found U+FFFD in order to set
    // mReplacementCharFallbackFamily[...] at all, but better play it safe
    if (fontEntry && fontEntry->HasCharacter(aCh)) {
      return fontEntry->FindOrMakeFont(aStyle);
    }
  }

  TimeStamp start = TimeStamp::Now();

  // search commonly available fonts
  bool common = true;
  FontFamily fallbackFamily;
  RefPtr<gfxFont> candidate =
      CommonFontFallback(aPresContext, aCh, aNextCh, aRunScript, aPresentation,
                         aStyle, fallbackFamily);
  RefPtr<gfxFont> font;
  if (candidate) {
    if (aPresentation == eFontPresentation::Any) {
      font = std::move(candidate);
    } else {
      bool hasColorGlyph = candidate->HasColorGlyphFor(aCh, aNextCh);
      if (hasColorGlyph == PrefersColor(aPresentation)) {
        font = std::move(candidate);
      }
    }
  }

  // If we didn't find a common font, or it was not the preferred type (color
  // or monochrome), do system-wide fallback (except for specials).
  uint32_t cmapCount = 0;
  if (!font) {
    common = false;
    font = GlobalFontFallback(aPresContext, aCh, aNextCh, aRunScript,
                              aPresentation, aStyle, cmapCount, fallbackFamily);
    // If the font we found doesn't match the requested type, and we also found
    // a candidate above, prefer that one.
    if (font && aPresentation != eFontPresentation::Any && candidate) {
      bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh);
      if (hasColorGlyph != PrefersColor(aPresentation)) {
        font = std::move(candidate);
      }
    }
  }
  TimeDuration elapsed = TimeStamp::Now() - start;

  LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);

  if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
    Script script = intl::UnicodeProperties::GetScriptCode(aCh);
    MOZ_LOG(log, LogLevel::Warning,
            ("(textrun-systemfallback-%s) char: u+%6.6x "
             "script: %d match: [%s]"
             " time: %dus cmaps: %d\n",
             (common ? "common" : "global"), aCh, static_cast<int>(script),
             (font ? font->GetFontEntry()->Name().get() : "<none>"),
             int32_t(elapsed.ToMicroseconds()), cmapCount));
  }

  // no match? add to set of non-matching codepoints
  if (!font) {
    mCodepointsWithNoFonts[level].set(aCh);
  } else {
    *aVisibility = fallbackFamily.mShared
                       ? fallbackFamily.mShared->Visibility()
                       : fallbackFamily.mUnshared->Visibility();
    if (aCh == 0xFFFD) {
      mReplacementCharFallbackFamily[level] = fallbackFamily;
    }
  }

  // track system fallback time
  static bool first = true;
  if (first)
    glean::fontlist::system_font_fallback_first.AccumulateRawDuration(elapsed);
  else
    glean::fontlist::system_font_fallback.AccumulateRawDuration(elapsed);
  first = false;

  return font.forget();
}

#define NUM_FALLBACK_FONTS 8

already_AddRefed<gfxFont> gfxPlatformFontList::CommonFontFallback(
    nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
    Script aRunScript, eFontPresentation aPresentation,
    const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) {
  AutoTArray<const char*, NUM_FALLBACK_FONTS> defaultFallbacks;
  gfxPlatform::GetPlatform()->GetCommonFallbackFonts(
      aCh, aRunScript, aPresentation, defaultFallbacks);
  GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation);
  FontVisibility level =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;

  // If a color-emoji presentation is requested, we will check any font found
  // to see if it can provide this; if not, we'll remember it as a possible
  // candidate but search the remainder of the list for a better choice.
  RefPtr<gfxFont> candidateFont;
  FontFamily candidateFamily;
  auto check = [&](gfxFontEntry* aFontEntry,
                   FontFamily aFamily) -> already_AddRefed<gfxFont> {
    RefPtr<gfxFont> font = aFontEntry->FindOrMakeFont(aMatchStyle);
    if (aPresentation < eFontPresentation::EmojiDefault ||
        font->HasColorGlyphFor(aCh, aNextCh)) {
      aMatchedFamily = aFamily;
      return font.forget();
    }
    // We want a color glyph but this font only has monochrome; remember it
    // (unless we already have a candidate) but continue to search.
    if (!candidateFont) {
      candidateFont = std::move(font);
      candidateFamily = aFamily;
    }
    return nullptr;
  };

  if (SharedFontList()) {
    for (const auto name : defaultFallbacks) {
      fontlist::Family* family =
          FindSharedFamily(aPresContext, nsDependentCString(name));
      if (!family || !IsVisibleToCSS(*family, level)) {
        continue;
      }
      // XXX(jfkthame) Should we fire the async cmap-loader here, or let it
      // always do a potential sync initialization of the family?
      family->SearchAllFontsForChar(SharedFontList(), &data);
      if (data.mBestMatch) {
        RefPtr<gfxFont> font = check(data.mBestMatch, FontFamily(family));
        if (font) {
          return font.forget();
        }
      }
    }
  } else {
    for (const auto name : defaultFallbacks) {
      gfxFontFamily* fallback =
          FindFamilyByCanonicalName(nsDependentCString(name));
      if (!fallback || !IsVisibleToCSS(*fallback, level)) {
        continue;
      }
      fallback->FindFontForChar(&data);
      if (data.mBestMatch) {
        RefPtr<gfxFont> font = check(data.mBestMatch, FontFamily(fallback));
        if (font) {
          return font.forget();
        }
      }
    }
  }

  // If we had a candidate that supports the character, but doesn't have the
  // desired emoji-style glyph, we'll return it anyhow as nothing better was
  // found.
  if (candidateFont) {
    aMatchedFamily = candidateFamily;
    return candidateFont.forget();
  }

  return nullptr;
}

already_AddRefed<gfxFont> gfxPlatformFontList::GlobalFontFallback(
    nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
    Script aRunScript, eFontPresentation aPresentation,
    const gfxFontStyle* aMatchStyle, uint32_t& aCmapCount,
    FontFamily& aMatchedFamily) {
  bool useCmaps = IsFontFamilyWhitelistActive() ||
                  gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
  FontVisibility level =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
  if (!useCmaps) {
    // Allow platform-specific fallback code to try and find a usable font
    gfxFontEntry* fe = PlatformGlobalFontFallback(aPresContext, aCh, aRunScript,
                                                  aMatchStyle, aMatchedFamily);
    if (fe) {
      if (aMatchedFamily.mShared) {
        if (IsVisibleToCSS(*aMatchedFamily.mShared, level)) {
          RefPtr<gfxFont> font = fe->FindOrMakeFont(aMatchStyle);
          if (font) {
            if (aPresentation == eFontPresentation::Any) {
              return font.forget();
            }
            bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh);
            if (hasColorGlyph == PrefersColor(aPresentation)) {
              return font.forget();
            }
          }
        }
      } else {
        if (IsVisibleToCSS(*aMatchedFamily.mUnshared, level)) {
          RefPtr<gfxFont> font = fe->FindOrMakeFont(aMatchStyle);
          if (font) {
            if (aPresentation == eFontPresentation::Any) {
              return font.forget();
            }
            bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh);
            if (hasColorGlyph == PrefersColor(aPresentation)) {
              return font.forget();
            }
          }
        }
      }
    }
  }

  // otherwise, try to find it among local fonts
  GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation);
  if (SharedFontList()) {
    fontlist::Family* families = SharedFontList()->Families();
    if (families) {
      for (uint32_t i = 0; i < SharedFontList()->NumFamilies(); i++) {
        fontlist::Family& family = families[i];
        if (!IsVisibleToCSS(family, level)) {
          continue;
        }
        if (!family.IsFullyInitialized() &&
            StaticPrefs::gfx_font_rendering_fallback_async() &&
            !XRE_IsParentProcess()) {
          // Start loading all the missing charmaps; but this is async,
          // so for now we just continue, ignoring this family.
          StartCmapLoadingFromFamily(i);
        } else {
          family.SearchAllFontsForChar(SharedFontList(), &data);
          if (data.mMatchDistance == 0.0) {
            // no better style match is possible, so stop searching
            break;
          }
        }
      }
      if (data.mBestMatch) {
        aMatchedFamily = FontFamily(data.mMatchedSharedFamily);
        return data.mBestMatch->FindOrMakeFont(aMatchStyle);
      }
    }
  } else {
    // iterate over all font families to find a font that support the
    // character
    for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
      if (!IsVisibleToCSS(*family, level)) {
        continue;
      }
      // evaluate all fonts in this family for a match
      family->FindFontForChar(&data);
      if (data.mMatchDistance == 0.0) {
        // no better style match is possible, so stop searching
        break;
      }
    }

    aCmapCount = data.mCmapsTested;
    if (data.mBestMatch) {
      aMatchedFamily = FontFamily(data.mMatchedFamily);
      return data.mBestMatch->FindOrMakeFont(aMatchStyle);
    }
  }

  return nullptr;
}

class StartCmapLoadingRunnable : public mozilla::Runnable {
 public:
  explicit StartCmapLoadingRunnable(uint32_t aStartIndex)
      : Runnable("gfxPlatformFontList::StartCmapLoadingRunnable"),
        mStartIndex(aStartIndex) {}

  NS_IMETHOD Run() override {
    auto* pfl = gfxPlatformFontList::PlatformFontList();
    auto* list = pfl->SharedFontList();
    if (!list) {
      return NS_OK;
    }
    if (mStartIndex >= list->NumFamilies()) {
      return NS_OK;
    }
    if (XRE_IsParentProcess()) {
      pfl->StartCmapLoading(list->GetGeneration(), mStartIndex);
    } else {
      dom::ContentChild::GetSingleton()->SendStartCmapLoading(
          list->GetGeneration(), mStartIndex);
    }
    return NS_OK;
  }

 private:
  uint32_t mStartIndex;
};

void gfxPlatformFontList::StartCmapLoadingFromFamily(uint32_t aStartIndex) {
  AutoLock lock(mLock);
  if (aStartIndex >= mStartedLoadingCmapsFrom) {
    // We already initiated cmap-loading from here or earlier in the list;
    // no need to do it again here.
    return;
  }
  mStartedLoadingCmapsFrom = aStartIndex;

  // If we're already on the main thread, don't bother dispatching a runnable
  // here to kick off the loading process, just do it directly.
  if (NS_IsMainThread()) {
    auto* list = SharedFontList();
    if (XRE_IsParentProcess()) {
      StartCmapLoading(list->GetGeneration(), aStartIndex);
    } else {
      dom::ContentChild::GetSingleton()->SendStartCmapLoading(
          list->GetGeneration(), aStartIndex);
    }
  } else {
    NS_DispatchToMainThread(new StartCmapLoadingRunnable(aStartIndex));
  }
}

class LoadCmapsRunnable final : public IdleRunnable,
                                public nsIObserver,
                                public nsSupportsWeakReference {
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIOBSERVER

 private:
  virtual ~LoadCmapsRunnable() {
    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
      obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
    }
  }

 public:
  LoadCmapsRunnable(uint32_t aGeneration, uint32_t aFamilyIndex)
      : IdleRunnable("gfxPlatformFontList::LoadCmapsRunnable"),
        mGeneration(aGeneration),
        mStartIndex(aFamilyIndex),
        mIndex(aFamilyIndex) {}

  void SetDeadline(TimeStamp aDeadline) override { mDeadline = aDeadline; }

  // Reset the current family index, if the value passed is earlier than our
  // original starting position. We don't "reset" if it would move the current
  // position forward, or back into the already-scanned range.
  // We could optimize further by remembering the current position reached,
  // and then skipping ahead from the original start, but it doesn't seem worth
  // extra complexity for a task that usually only happens once, and already-
  // processed families will be skipped pretty quickly in Run() anyhow.
  void MaybeResetIndex(uint32_t aFamilyIndex) {
    if (aFamilyIndex < mStartIndex) {
      mStartIndex = aFamilyIndex;
      mIndex = aFamilyIndex;
    }
  }

  void Cancel() { mIsCanceled = true; }

  NS_IMETHOD Run() override {
    if (mIsCanceled) {
      return NS_OK;
    }
    auto* pfl = gfxPlatformFontList::PlatformFontList();
    auto* list = pfl->SharedFontList();
    MOZ_ASSERT(list);
    if (!list) {
      return NS_OK;
    }
    if (mGeneration != list->GetGeneration()) {
      return NS_OK;
    }
    uint32_t numFamilies = list->NumFamilies();
    if (mIndex >= numFamilies) {
      return NS_OK;
    }
    auto* families = list->Families();
    while (mIndex < numFamilies) {
      auto& family = families[mIndex++];
      if (family.IsFullyInitialized()) {
        // Skip any families that are already initialized.
        continue;
      }
      // Fully initialize this family.
      Unused << pfl->InitializeFamily(&family, true);
      // TODO(emilio): It'd make sense to use mDeadline here to determine
      // whether we can do more work, but that is surprisingly a performance
      // regression in practice, see bug 1936489. Investigate if we can be
      // smarter about this.
      break;
    }
    // If there are more families to initialize, post ourselves back to the
    // idle queue handle the next ones; otherwise we're finished and we need
    // to notify content processes to update their rendering.
    if (mIndex < numFamilies) {
      mDeadline = TimeStamp();
      NS_DispatchToMainThreadQueue(do_AddRef(this), EventQueuePriority::Idle);
    } else {
      pfl->Lock();
      pfl->CancelLoadCmapsTask();
      pfl->InitializeCodepointsWithNoFonts();
      dom::ContentParent::NotifyUpdatedFonts(false);
      pfl->Unlock();
    }
    return NS_OK;
  }

 private:
  uint32_t mGeneration;
  uint32_t mStartIndex;
  uint32_t mIndex;
  TimeStamp mDeadline;
  bool mIsCanceled = false;
};

NS_IMPL_ISUPPORTS_INHERITED(LoadCmapsRunnable, IdleRunnable, nsIObserver,
                            nsISupportsWeakReference);

NS_IMETHODIMP
LoadCmapsRunnable::Observe(nsISupports* aSubject, const char* aTopic,
                           const char16_t* aData) {
  MOZ_ASSERT(!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID),
             "unexpected topic");
  Cancel();
  return NS_OK;
}

void gfxPlatformFontList::CancelLoadCmapsTask() {
  if (mLoadCmapsRunnable) {
    mLoadCmapsRunnable->Cancel();
    mLoadCmapsRunnable = nullptr;
  }
}

void gfxPlatformFontList::StartCmapLoading(uint32_t aGeneration,
                                           uint32_t aStartIndex) {
  MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
  if (aGeneration != SharedFontList()->GetGeneration()) {
    return;
  }
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return;
  }
  if (mLoadCmapsRunnable) {
    // We already have a runnable; just make sure it covers the full range of
    // families needed.
    mLoadCmapsRunnable->MaybeResetIndex(aStartIndex);
    return;
  }
  mLoadCmapsRunnable = new LoadCmapsRunnable(aGeneration, aStartIndex);
  if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
    obs->AddObserver(mLoadCmapsRunnable, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
                     /* ownsWeak = */ true);
  }
  NS_DispatchToMainThreadQueue(do_AddRef(mLoadCmapsRunnable),
                               EventQueuePriority::Idle);
}

gfxFontFamily* gfxPlatformFontList::CheckFamily(gfxFontFamily* aFamily) {
  if (aFamily && !aFamily->HasStyles()) {
    aFamily->FindStyleVariations();
  }

  if (aFamily && aFamily->FontListLength() == 0) {
    // Failed to load any faces for this family, so discard it.
    nsAutoCString key;
    GenerateFontListKey(aFamily->Name(), key);
    mFontFamilies.Remove(key);
    return nullptr;
  }

  return aFamily;
}

bool gfxPlatformFontList::FindAndAddFamilies(
    nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
    const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
    FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
    gfxFloat aDevToCssSize) {
  AutoLock lock(mLock);

#ifdef DEBUG
  auto initialLength = aOutput->Length();
#endif

  bool didFind =
      FindAndAddFamiliesLocked(aPresContext, aGeneric, aFamily, aOutput, aFlags,
                               aStyle, aLanguage, aDevToCssSize);
#ifdef DEBUG
  auto finalLength = aOutput->Length();
  // Validate the expectation that the output-array grows if we return true,
  // or remains the same (probably empty) if we return false.
  MOZ_ASSERT_IF(didFind, finalLength > initialLength);
  MOZ_ASSERT_IF(!didFind, finalLength == initialLength);
#endif

  return didFind;
}

bool gfxPlatformFontList::FindAndAddFamiliesLocked(
    nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
    const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
    FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
    gfxFloat aDevToCssSize) {
  nsAutoCString key;
  GenerateFontListKey(aFamily, key);

  bool allowHidden = bool(aFlags & FindFamiliesFlags::eSearchHiddenFamilies);
  FontVisibility visibilityLevel =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;

  // If this font lookup is the result of resolving a CSS generic (not a direct
  // font-family request by the page), and RFP settings allow generics to be
  // unrestricted, bump the effective visibility level applied here so as to
  // allow user-installed fonts to be used.
  if (visibilityLevel < FontVisibility::User &&
      aGeneric != StyleGenericFontFamily::None &&
      !aPresContext->Document()->ShouldResistFingerprinting(
          RFPTarget::FontVisibilityRestrictGenerics)) {
    visibilityLevel = FontVisibility::User;
  }

  if (SharedFontList()) {
    fontlist::Family* family = SharedFontList()->FindFamily(key);
    // If not found, and other family names have not yet been initialized,
    // initialize the rest of the list and try again. This is done lazily
    // since reading name table entries is expensive.
    // Although ASCII localized family names are possible they don't occur
    // in practice, so avoid pulling in names at startup.
    if (!family && !mOtherFamilyNamesInitialized) {
      bool triggerLoading = true;
      bool mayDefer =
          !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading);
      if (IsAscii(key)) {
        // If `key` is an ASCII name, only trigger loading if it includes a
        // space, and the "base" name (up to the last space) exists as a known
        // family, so that this might be a legacy styled-family name.
        const char* data = key.BeginReading();
        int32_t index = key.Length();
        while (--index > 0) {
          if (data[index] == ' ') {
            break;
          }
        }
        if (index <= 0 ||
            !SharedFontList()->FindFamily(nsAutoCString(key.get(), index))) {
          triggerLoading = false;
        }
      }
      if (triggerLoading) {
        if (InitOtherFamilyNames(mayDefer)) {
          family = SharedFontList()->FindFamily(key);
        }
      }
      if (!family && !mOtherFamilyNamesInitialized &&
          !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) {
        AddToMissedNames(key);
      }
    }
    // Check whether the family we found is actually allowed to be looked up,
    // according to current font-visibility prefs.
    if (family) {
      bool visible = IsVisibleToCSS(*family, visibilityLevel);
      if (visible || (allowHidden && family->IsHidden())) {
        aOutput->AppendElement(FamilyAndGeneric(family, aGeneric));
        return true;
      }
      if (aPresContext) {
        aPresContext->ReportBlockedFontFamily(*family);
      }
    }
    return false;
  }

  NS_ASSERTION(mFontFamilies.Count() != 0,
               "system font list was not initialized correctly");

  auto isBlockedByVisibilityLevel = [=](gfxFontFamily* aFamily) -> bool {
    bool visible = IsVisibleToCSS(*aFamily, visibilityLevel);
    if (visible || (allowHidden && aFamily->IsHidden())) {
      return false;
    }
    if (aPresContext) {
      aPresContext->ReportBlockedFontFamily(*aFamily);
    }
    return true;
  };

  // lookup in canonical (i.e. English) family name list
  gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
  if (familyEntry) {
    if (isBlockedByVisibilityLevel(familyEntry)) {
      return false;
    }
  }

  // if not found, lookup in other family names list (mostly localized names)
  if (!familyEntry) {
    familyEntry = mOtherFamilyNames.GetWeak(key);
  }
  if (familyEntry) {
    if (isBlockedByVisibilityLevel(familyEntry)) {
      return false;
    }
  }

  // if still not found and other family names not yet fully initialized,
  // initialize the rest of the list and try again.  this is done lazily
  // since reading name table entries is expensive.
  // although ASCII localized family names are possible they don't occur
  // in practice so avoid pulling in names at startup
  if (!familyEntry && !mOtherFamilyNamesInitialized && !IsAscii(aFamily)) {
    InitOtherFamilyNames(
        !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading));
    familyEntry = mOtherFamilyNames.GetWeak(key);
    if (!familyEntry && !mOtherFamilyNamesInitialized &&
        !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) {
      // localized family names load timed out, add name to list of
      // names to check after localized names are loaded
      AddToMissedNames(key);
    }
    if (familyEntry) {
      if (isBlockedByVisibilityLevel(familyEntry)) {
        return false;
      }
    }
  }

  familyEntry = CheckFamily(familyEntry);

  // If we failed to find the requested family, check for a space in the
  // name; if found, and if the "base" name (up to the last space) exists
  // as a family, then this might be a legacy GDI-style family name for
  // an additional weight/width. Try searching the faces of the base family
  // and create any corresponding legacy families.
  if (!familyEntry &&
      !(aFlags & FindFamiliesFlags::eNoSearchForLegacyFamilyNames)) {
    // We don't have nsAString::RFindChar, so look for a space manually
    const char* data = aFamily.BeginReading();
    int32_t index = aFamily.Length();
    while (--index > 0) {
      if (data[index] == ' ') {
        break;
      }
    }
    if (index > 0) {
      gfxFontFamily* base =
          FindUnsharedFamily(aPresContext, Substring(aFamily, 0, index),
                             FindFamiliesFlags::eNoSearchForLegacyFamilyNames);
      // If we found the "base" family name, and if it has members with
      // legacy names, this will add corresponding font-family entries to
      // the mOtherFamilyNames list; then retry the legacy-family search.
      if (base && base->CheckForLegacyFamilyNames(this)) {
        familyEntry = mOtherFamilyNames.GetWeak(key);
      }
      if (familyEntry) {
        if (isBlockedByVisibilityLevel(familyEntry)) {
          return false;
        }
      }
    }
  }

  if (familyEntry) {
    aOutput->AppendElement(FamilyAndGeneric(familyEntry, aGeneric));
    return true;
  }

  return false;
}

void gfxPlatformFontList::AddToMissedNames(const nsCString& aKey) {
  if (!mOtherNamesMissed) {
    mOtherNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2);
  }
  mOtherNamesMissed->Insert(aKey);
}

fontlist::Family* gfxPlatformFontList::FindSharedFamily(
    nsPresContext* aPresContext, const nsACString& aFamily,
    FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
    gfxFloat aDevToCss) {
  if (!SharedFontList()) {
    return nullptr;
  }
  AutoTArray<FamilyAndGeneric, 1> families;
  if (!FindAndAddFamiliesLocked(aPresContext, StyleGenericFontFamily::None,
                                aFamily, &families, aFlags, aStyle, aLanguage,
                                aDevToCss) ||
      !families[0].mFamily.mShared) {
    return nullptr;
  }
  fontlist::Family* family = families[0].mFamily.mShared;
  if (!family->IsInitialized()) {
    if (!InitializeFamily(family)) {
      return nullptr;
    }
  }
  return family;
}

class InitializeFamilyRunnable : public mozilla::Runnable {
 public:
  explicit InitializeFamilyRunnable(uint32_t aFamilyIndex, bool aLoadCmaps)
      : Runnable("gfxPlatformFontList::InitializeFamilyRunnable"),
        mIndex(aFamilyIndex),
        mLoadCmaps(aLoadCmaps) {}

  NS_IMETHOD Run() override {
    auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
    if (!list) {
      return NS_OK;
    }
    if (mIndex >= list->NumFamilies()) {
      // Out of range? Maybe the list got reinitialized since this request
      // was posted - just ignore it.
      return NS_OK;
    }
    dom::ContentChild::GetSingleton()->SendInitializeFamily(
        list->GetGeneration(), mIndex, mLoadCmaps);
    return NS_OK;
  }

 private:
  uint32_t mIndex;
  bool mLoadCmaps;
};

bool gfxPlatformFontList::InitializeFamily(fontlist::Family* aFamily,
                                           bool aLoadCmaps) {
  MOZ_ASSERT(SharedFontList());
  auto list = SharedFontList();
  if (!XRE_IsParentProcess()) {
    auto* families = list->Families();
    if (!families) {
      return false;
    }
    uint32_t index = aFamily - families;
    if (index >= list->NumFamilies()) {
      return false;
    }
    if (NS_IsMainThread()) {
      dom::ContentChild::GetSingleton()->SendInitializeFamily(
          list->GetGeneration(), index, aLoadCmaps);
    } else {
      NS_DispatchToMainThread(new InitializeFamilyRunnable(index, aLoadCmaps));
    }
    return aFamily->IsInitialized();
  }

  if (!aFamily->IsInitialized()) {
    // The usual case: we're being asked to populate the face list.
    AutoTArray<fontlist::Face::InitData, 16> faceList;
    GetFacesInitDataForFamily(aFamily, faceList, aLoadCmaps);
    aFamily->AddFaces(list, faceList);
  } else {
    // The family's face list was already initialized, but if aLoadCmaps is
    // true we also want to eagerly load character maps. This is used when a
    // child process is doing SearchAllFontsForChar, to have the parent load
    // all the cmaps at once and reduce IPC traffic (and content-process file
    // access overhead, which is crippling for DirectWrite on Windows).
    if (aLoadCmaps) {
      auto* faces = aFamily->Faces(list);
      if (faces) {
        for (size_t i = 0; i < aFamily->NumFaces(); i++) {
          auto* face = faces[i].ToPtr<fontlist::Face>(list);
          if (face && face->mCharacterMap.IsNull()) {
            // We don't want to cache this font entry, as the parent will most
            // likely never use it again; it's just to populate the charmap for
            // the benefit of the child process.
            RefPtr<gfxFontEntry> fe = CreateFontEntry(face, aFamily);
            if (fe) {
              fe->ReadCMAP();
            }
          }
        }
      }
    }
  }

  if (aLoadCmaps && aFamily->IsInitialized()) {
    aFamily->SetupFamilyCharMap(list);
  }

  return aFamily->IsInitialized();
}

gfxFontEntry* gfxPlatformFontList::FindFontForFamily(
    nsPresContext* aPresContext, const nsACString& aFamily,
    const gfxFontStyle* aStyle) {
  AutoLock lock(mLock);

  nsAutoCString key;
  GenerateFontListKey(aFamily, key);

  FontFamily family = FindFamily(aPresContext, key);
  if (family.IsNull()) {
    return nullptr;
  }
  if (family.mShared) {
    auto face = family.mShared->FindFaceForStyle(SharedFontList(), *aStyle);
    if (!face) {
      return nullptr;
    }
    return GetOrCreateFontEntryLocked(face, family.mShared);
  }
  return family.mUnshared->FindFontForStyle(*aStyle);
}

gfxFontEntry* gfxPlatformFontList::GetOrCreateFontEntryLocked(
    fontlist::Face* aFace, const fontlist::Family* aFamily) {
  return mFontEntries
      .LookupOrInsertWith(aFace,
                          [=] { return CreateFontEntry(aFace, aFamily); })
      .get();
}

void gfxPlatformFontList::AddOtherFamilyNames(
    gfxFontFamily* aFamilyEntry, const nsTArray<nsCString>& aOtherFamilyNames) {
  AutoLock lock(mLock);

  for (const auto& name : aOtherFamilyNames) {
    nsAutoCString key;
    GenerateFontListKey(name, key);

    mOtherFamilyNames.LookupOrInsertWith(key, [&] {
      LOG_FONTLIST(
          ("(fontlist-otherfamily) canonical family: %s, other family: %s\n",
           aFamilyEntry->Name().get(), name.get()));
      if (mBadUnderlineFamilyNames.ContainsSorted(key)) {
        aFamilyEntry->SetBadUnderlineFamily();
      }
      return RefPtr{aFamilyEntry};
    });
  }
}

void gfxPlatformFontList::AddFullnameLocked(gfxFontEntry* aFontEntry,
                                            const nsCString& aFullname) {
  mExtraNames->mFullnames.LookupOrInsertWith(aFullname, [&] {
    LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n",
                  aFontEntry->Name().get(), aFullname.get()));
    return RefPtr{aFontEntry};
  });
}

void gfxPlatformFontList::AddPostscriptNameLocked(
    gfxFontEntry* aFontEntry, const nsCString& aPostscriptName) {
  mExtraNames->mPostscriptNames.LookupOrInsertWith(aPostscriptName, [&] {
    LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n",
                  aFontEntry->Name().get(), aPostscriptName.get()));
    return RefPtr{aFontEntry};
  });
}

bool gfxPlatformFontList::GetStandardFamilyName(const nsCString& aFontName,
                                                nsACString& aFamilyName) {
  AutoLock lock(mLock);
  FontFamily family = FindFamily(nullptr, aFontName);
  if (family.IsNull()) {
    return false;
  }
  return GetLocalizedFamilyName(family, aFamilyName);
}

bool gfxPlatformFontList::GetLocalizedFamilyName(const FontFamily& aFamily,
                                                 nsACString& aFamilyName) {
  if (aFamily.mShared) {
    aFamilyName = SharedFontList()->LocalizedFamilyName(aFamily.mShared);
    return true;
  }
  if (aFamily.mUnshared) {
    aFamily.mUnshared->LocalizedName(aFamilyName);
    return true;
  }
  return false;  // leaving the aFamilyName outparam untouched
}

FamilyAndGeneric gfxPlatformFontList::GetDefaultFontFamily(
    const nsACString& aLangGroup, const nsACString& aGenericFamily) {
  if (NS_WARN_IF(aLangGroup.IsEmpty()) ||
      NS_WARN_IF(aGenericFamily.IsEmpty())) {
    return FamilyAndGeneric();
  }

  AutoLock lock(mLock);

  nsAutoCString value;
  AutoTArray<nsCString, 4> names;
  if (mFontPrefs->LookupNameList(PrefName(aGenericFamily, aLangGroup), value)) {
    gfxFontUtils::ParseFontList(value, names);
  }

  for (const nsCString& name : names) {
    FontFamily family = FindFamily(nullptr, name);
    if (!family.IsNull()) {
      return FamilyAndGeneric(family);
    }
  }

  return FamilyAndGeneric();
}

ShmemCharMapHashEntry::ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap)
    : mList(gfxPlatformFontList::PlatformFontList()->SharedFontList()),
      mHash(aCharMap->GetChecksum()) {
  size_t len = SharedBitSet::RequiredSize(*aCharMap);
  mCharMap = mList->Alloc(len);
  SharedBitSet::Create(mCharMap.ToPtr(mList, len), len, *aCharMap);
}

fontlist::Pointer gfxPlatformFontList::GetShmemCharMapLocked(
    const gfxSparseBitSet* aCmap) {
  auto* entry = mShmemCharMaps.GetEntry(aCmap);
  if (!entry) {
    entry = mShmemCharMaps.PutEntry(aCmap);
  }
  return entry->GetCharMap();
}

// Lookup aCmap in the shared cmap set, adding if not already present.
// This is the only way for a reference to a gfxCharacterMap to be acquired
// by another thread than its original creator.
already_AddRefed<gfxCharacterMap> gfxPlatformFontList::FindCharMap(
    gfxCharacterMap* aCmap) {
  // Lock to prevent potentially racing against MaybeRemoveCmap.
  AutoLock lock(mLock);

  // Find existing entry or insert a new one (which will add a reference).
  aCmap->CalcHash();
  aCmap->mShared = true;  // Set the shared flag in preparation for adding
                          // to the global table.
  RefPtr cmap = mSharedCmaps.PutEntry(aCmap)->GetKey();

  // If we ended up finding a different, pre-existing entry, clear the
  // shared flag on this one so that it'll get deleted on Release().
  if (cmap.get() != aCmap) {
    aCmap->mShared = false;
  }

  return cmap.forget();
}

// Potentially remove the charmap from the shared cmap set. This is called
// when a user of the charmap drops a reference and the refcount goes to 1;
// in that case, it is possible our shared set is the only remaining user
// of the object, and we should remove it.
// Note that aCharMap might have already been freed, so we must not try to
// dereference it until we have checked that it's still present in our table.
void gfxPlatformFontList::MaybeRemoveCmap(gfxCharacterMap* aCharMap) {
  // Lock so that nobody else can get a reference via FindCharMap while we're
  // checking here.
  AutoLock lock(mLock);

  // Skip lookups during teardown.
  if (!mSharedCmaps.Count()) {
    return;
  }

  // aCharMap needs to match the entry and be the same ptr and still have a
  // refcount of exactly 1 (i.e. we hold the only reference) before removing.
  // If we're racing another thread, it might already have been removed, in
  // which case GetEntry will not find it and we won't try to dereference the
  // already-freed pointer.
  CharMapHashKey* found =
      mSharedCmaps.GetEntry(const_cast<gfxCharacterMap*>(aCharMap));
  if (found && found->GetKey() == aCharMap && aCharMap->RefCount() == 1) {
    // Forget our reference to the object that's being deleted, without
    // calling Release() on it.
    Unused << found->mCharMap.forget();

    // Do the deletion.
    delete aCharMap;

    // Log this as a "Release" to keep leak-checking correct.
    NS_LOG_RELEASE(aCharMap, 0, "gfxCharacterMap");

    mSharedCmaps.RemoveEntry(found);
  }
}

static void GetSystemUIFontFamilies(const nsPresContext* aPresContext,
                                    [[maybe_unused]] nsAtom* aLangGroup,
                                    nsTArray<nsCString>& aFamilies) {
  // TODO: On macOS, use CTCreateUIFontForLanguage or such thing (though the
  // code below ends up using [NSFont systemFontOfSize: 0.0].
  nsFont systemFont;
  gfxFontStyle fontStyle;
  nsAutoString systemFontName;
  if (aPresContext ? aPresContext->Document()->ShouldResistFingerprinting(
                         RFPTarget::FontVisibilityRestrictGenerics)
                   : nsContentUtils::ShouldResistFingerprinting(
                         "aPresContext not available",
                         RFPTarget::FontVisibilityRestrictGenerics)) {
#if defined(XP_MACOSX) || defined(MOZ_WIDGET_UIKIT)
    *aFamilies.AppendElement() = "-apple-system"_ns;
    return;
#elif !defined(MOZ_WIDGET_ANDROID)
    *aFamilies.AppendElement() = "sans-serif"_ns;
    return;
#endif
  }
  if (!LookAndFeel::GetFont(StyleSystemFont::Menu, systemFontName, fontStyle)) {
    return;
  }
  systemFontName.Trim("\"'");
  CopyUTF16toUTF8(systemFontName, *aFamilies.AppendElement());
}

void gfxPlatformFontList::ResolveGenericFontNames(
    nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
    eFontPrefLang aPrefLang, PrefFontList* aGenericFamilies) {
  const char* langGroupStr = GetPrefLangName(aPrefLang);
  const char* generic = GetGenericName(aGenericType);

  if (!generic) {
    return;
  }

  AutoTArray<nsCString, 4> genericFamilies;

  // load family for "font.name.generic.lang"
  PrefName prefName(generic, langGroupStr);
  nsAutoCString value;
  if (mFontPrefs->LookupName(prefName, value)) {
    gfxFontUtils::ParseFontList(value, genericFamilies);
  }

  // load fonts for "font.name-list.generic.lang"
  if (mFontPrefs->LookupNameList(prefName, value)) {
    gfxFontUtils::ParseFontList(value, genericFamilies);
  }

  nsAtom* langGroup = GetLangGroupForPrefLang(aPrefLang);
  MOZ_ASSERT(langGroup, "null lang group for pref lang");

  if (aGenericType == StyleGenericFontFamily::SystemUi) {
    GetSystemUIFontFamilies(aPresContext, langGroup, genericFamilies);
  }

  GetFontFamiliesFromGenericFamilies(
      aPresContext, aGenericType, genericFamilies, langGroup, aGenericFamilies);

#if 0  // dump out generic mappings
    printf("%s ===> ", NamePref(generic, langGroupStr).get());
    for (uint32_t k = 0; k < aGenericFamilies->Length(); k++) {
        if (k > 0) printf(", ");
        printf("%s", (*aGenericFamilies)[k].mIsShared
            ? (*aGenericFamilies)[k].mShared->DisplayName().AsString(SharedFontList()).get()
            : (*aGenericFamilies)[k].mUnshared->Name().get());
    }
    printf("\n");
#endif
}

void gfxPlatformFontList::ResolveEmojiFontNames(
    nsPresContext* aPresContext, PrefFontList* aGenericFamilies) {
  // emoji preference has no lang name
  AutoTArray<nsCString, 4> genericFamilies;

  nsAutoCString value;
  if (mFontPrefs->LookupNameList(PrefName("emoji"""), value)) {
    gfxFontUtils::ParseFontList(value, genericFamilies);
  }

  GetFontFamiliesFromGenericFamilies(
      aPresContext, StyleGenericFontFamily::MozEmoji, genericFamilies, nullptr,
      aGenericFamilies);
}

void gfxPlatformFontList::GetFontFamiliesFromGenericFamilies(
    nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
    nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup,
    PrefFontList* aGenericFamilies) {
  // lookup and add platform fonts uniquely
  for (const nsCString& genericFamily : aGenericNameFamilies) {
    AutoTArray<FamilyAndGeneric, 10> families;
    FindAndAddFamiliesLocked(aPresContext, aGenericType, genericFamily,
                             &families, FindFamiliesFlags(0), nullptr,
                             aLangGroup);
    for (const FamilyAndGeneric& f : families) {
      if (!aGenericFamilies->Contains(f.mFamily)) {
        aGenericFamilies->AppendElement(f.mFamily);
      }
    }
  }
}

gfxPlatformFontList::PrefFontList*
gfxPlatformFontList::GetPrefFontsLangGroupLocked(
    nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
    eFontPrefLang aPrefLang) {
  if (aGenericType == StyleGenericFontFamily::MozEmoji ||
      aPrefLang == eFontPrefLang_Emoji) {
    // Emoji font has no lang
    PrefFontList* prefFonts = mEmojiPrefFont.get();
    if (MOZ_UNLIKELY(!prefFonts)) {
      prefFonts = new PrefFontList;
      ResolveEmojiFontNames(aPresContext, prefFonts);
      mEmojiPrefFont.reset(prefFonts);
    }
    return prefFonts;
  }

  auto index = static_cast<size_t>(aGenericType);
  PrefFontList* prefFonts = mLangGroupPrefFonts[aPrefLang][index].get();
  if (MOZ_UNLIKELY(!prefFonts)) {
    prefFonts = new PrefFontList;
    ResolveGenericFontNames(aPresContext, aGenericType, aPrefLang, prefFonts);
    mLangGroupPrefFonts[aPrefLang][index].reset(prefFonts);
  }
  return prefFonts;
}

void gfxPlatformFontList::AddGenericFonts(
    nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
    nsAtom* aLanguage, nsTArray<FamilyAndGeneric>& aFamilyList) {
  AutoLock lock(mLock);

  // map lang ==> langGroup
  nsAtom* langGroup = GetLangGroup(aLanguage);

  // langGroup ==> prefLang
  eFontPrefLang prefLang = GetFontPrefLangFor(langGroup);

  // lookup pref fonts
  PrefFontList* prefFonts =
      GetPrefFontsLangGroupLocked(aPresContext, aGenericType, prefLang);

  if (!prefFonts->IsEmpty()) {
    aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length());
    for (auto& f : *prefFonts) {
      aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType));
    }
  }
}

static nsAtom* PrefLangToLangGroups(uint32_t aIndex) {
  // static array here avoids static constructor
  static nsAtom* gPrefLangToLangGroups[] = {
#define FONT_PREF_LANG(enum_id_, str_, atom_id_) nsGkAtoms::atom_id_
#include "gfxFontPrefLangList.h"
#undef FONT_PREF_LANG
  };

  return aIndex < std::size(gPrefLangToLangGroups)
             ? gPrefLangToLangGroups[aIndex]
             : nsGkAtoms::Unicode;
}

eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(const char* aLang) {
  if (!aLang || !aLang[0]) {
    return eFontPrefLang_Others;
  }
  for (uint32_t i = 0; i < std::size(gPrefLangNames); ++i) {
    if (!nsCRT::strcasecmp(gPrefLangNames[i], aLang)) {
      return eFontPrefLang(i);
    }
    // If the pref-lang is a two-character lang tag, try ignoring any trailing
    // subtags in aLang and see if the base lang matches.
    if (strlen(gPrefLangNames[i]) == 2 && strlen(aLang) > 3 &&
        aLang[2] == '-' && !nsCRT::strncasecmp(gPrefLangNames[i], aLang, 2)) {
      return eFontPrefLang(i);
    }
  }
  return eFontPrefLang_Others;
}

eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(nsAtom* aLang) {
  if (!aLang) return eFontPrefLang_Others;
  nsAutoCString lang;
  aLang->ToUTF8String(lang);
  return GetFontPrefLangFor(lang.get());
}

nsAtom* gfxPlatformFontList::GetLangGroupForPrefLang(eFontPrefLang aLang) {
  // the special CJK set pref lang should be resolved into separate
  // calls to individual CJK pref langs before getting here
  NS_ASSERTION(aLang != eFontPrefLang_CJKSet, "unresolved CJK set pref lang");

  return PrefLangToLangGroups(uint32_t(aLang));
}

const char* gfxPlatformFontList::GetPrefLangName(eFontPrefLang aLang) {
  if (uint32_t(aLang) < std::size(gPrefLangNames)) {
    return gPrefLangNames[uint32_t(aLang)];
  }
  return nullptr;
}

eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(uint32_t aCh) {
  switch (ublock_getCode(aCh)) {
    case UBLOCK_BASIC_LATIN:
    case UBLOCK_LATIN_1_SUPPLEMENT:
    case UBLOCK_LATIN_EXTENDED_A:
    case UBLOCK_LATIN_EXTENDED_B:
    case UBLOCK_IPA_EXTENSIONS:
    case UBLOCK_SPACING_MODIFIER_LETTERS:
    case UBLOCK_LATIN_EXTENDED_ADDITIONAL:
    case UBLOCK_LATIN_EXTENDED_C:
    case UBLOCK_LATIN_EXTENDED_D:
    case UBLOCK_LATIN_EXTENDED_E:
    case UBLOCK_PHONETIC_EXTENSIONS:
      return eFontPrefLang_Western;
    case UBLOCK_GREEK:
    case UBLOCK_GREEK_EXTENDED:
      return eFontPrefLang_Greek;
    case UBLOCK_CYRILLIC:
    case UBLOCK_CYRILLIC_SUPPLEMENT:
    case UBLOCK_CYRILLIC_EXTENDED_A:
    case UBLOCK_CYRILLIC_EXTENDED_B:
    case UBLOCK_CYRILLIC_EXTENDED_C:
      return eFontPrefLang_Cyrillic;
    case UBLOCK_ARMENIAN:
      return eFontPrefLang_Armenian;
    case UBLOCK_HEBREW:
      return eFontPrefLang_Hebrew;
    case UBLOCK_ARABIC:
    case UBLOCK_ARABIC_PRESENTATION_FORMS_A:
    case UBLOCK_ARABIC_PRESENTATION_FORMS_B:
    case UBLOCK_ARABIC_SUPPLEMENT:
    case UBLOCK_ARABIC_EXTENDED_A:
    case UBLOCK_ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS:
      return eFontPrefLang_Arabic;
    case UBLOCK_DEVANAGARI:
    case UBLOCK_DEVANAGARI_EXTENDED:
      return eFontPrefLang_Devanagari;
    case UBLOCK_BENGALI:
      return eFontPrefLang_Bengali;
    case UBLOCK_GURMUKHI:
      return eFontPrefLang_Gurmukhi;
    case UBLOCK_GUJARATI:
      return eFontPrefLang_Gujarati;
    case UBLOCK_ORIYA:
      return eFontPrefLang_Oriya;
    case UBLOCK_TAMIL:
      return eFontPrefLang_Tamil;
    case UBLOCK_TELUGU:
      return eFontPrefLang_Telugu;
    case UBLOCK_KANNADA:
      return eFontPrefLang_Kannada;
    case UBLOCK_MALAYALAM:
      return eFontPrefLang_Malayalam;
    case UBLOCK_SINHALA:
    case UBLOCK_SINHALA_ARCHAIC_NUMBERS:
      return eFontPrefLang_Sinhala;
    case UBLOCK_THAI:
      return eFontPrefLang_Thai;
    case UBLOCK_TIBETAN:
      return eFontPrefLang_Tibetan;
    case UBLOCK_GEORGIAN:
    case UBLOCK_GEORGIAN_SUPPLEMENT:
    case UBLOCK_GEORGIAN_EXTENDED:
      return eFontPrefLang_Georgian;
    case UBLOCK_HANGUL_JAMO:
    case UBLOCK_HANGUL_COMPATIBILITY_JAMO:
    case UBLOCK_HANGUL_SYLLABLES:
    case UBLOCK_HANGUL_JAMO_EXTENDED_A:
    case UBLOCK_HANGUL_JAMO_EXTENDED_B:
      return eFontPrefLang_Korean;
    case UBLOCK_ETHIOPIC:
    case UBLOCK_ETHIOPIC_EXTENDED:
    case UBLOCK_ETHIOPIC_SUPPLEMENT:
    case UBLOCK_ETHIOPIC_EXTENDED_A:
      return eFontPrefLang_Ethiopic;
    case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS:
    case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED:
      return eFontPrefLang_Canadian;
    case UBLOCK_KHMER:
    case UBLOCK_KHMER_SYMBOLS:
      return eFontPrefLang_Khmer;
    case UBLOCK_CJK_RADICALS_SUPPLEMENT:
    case UBLOCK_KANGXI_RADICALS:
    case UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS:
    case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION:
    case UBLOCK_HIRAGANA:
    case UBLOCK_KATAKANA:
    case UBLOCK_BOPOMOFO:
    case UBLOCK_KANBUN:
    case UBLOCK_BOPOMOFO_EXTENDED:
    case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS:
    case UBLOCK_CJK_COMPATIBILITY:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS:
    case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS:
    case UBLOCK_CJK_COMPATIBILITY_FORMS:
    case UBLOCK_SMALL_FORM_VARIANTS:
    case UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B:
    case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT:
    case UBLOCK_KATAKANA_PHONETIC_EXTENSIONS:
    case UBLOCK_CJK_STROKES:
    case UBLOCK_VERTICAL_FORMS:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C:
    case UBLOCK_KANA_SUPPLEMENT:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E:
    case UBLOCK_IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION:
    case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F:
    case UBLOCK_KANA_EXTENDED_A:
      return eFontPrefLang_CJKSet;
    case UBLOCK_MATHEMATICAL_OPERATORS:
    case UBLOCK_MATHEMATICAL_ALPHANUMERIC_SYMBOLS:
    case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A:
    case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B:
    case UBLOCK_SUPPLEMENTAL_MATHEMATICAL_OPERATORS:
      return eFontPrefLang_Mathematics;
    default:
      return eFontPrefLang_Others;
  }
}

bool gfxPlatformFontList::IsLangCJK(eFontPrefLang aLang) {
  switch (aLang) {
    case eFontPrefLang_Japanese:
    case eFontPrefLang_ChineseTW:
    case eFontPrefLang_ChineseCN:
    case eFontPrefLang_ChineseHK:
    case eFontPrefLang_Korean:
    case eFontPrefLang_CJKSet:
      return true;
    default:
      return false;
  }
}

void gfxPlatformFontList::GetLangPrefs(eFontPrefLang aPrefLangs[],
                                       uint32_t& aLen, eFontPrefLang aCharLang,
                                       eFontPrefLang aPageLang) {
  AutoLock lock(mLock);
  if (IsLangCJK(aCharLang)) {
    AppendCJKPrefLangs(aPrefLangs, aLen, aCharLang, aPageLang);
  } else {
    AppendPrefLang(aPrefLangs, aLen, aCharLang);
  }

  AppendPrefLang(aPrefLangs, aLen, eFontPrefLang_Others);
}

void gfxPlatformFontList::AppendCJKPrefLangs(eFontPrefLang aPrefLangs[],
                                             uint32_t& aLen,
                                             eFontPrefLang aCharLang,
                                             eFontPrefLang aPageLang) {
  // prefer the lang specified by the page *if* CJK
  if (IsLangCJK(aPageLang)) {
    AppendPrefLang(aPrefLangs, aLen, aPageLang);
  }

  // if not set up, set up the default CJK order, based on accept lang
  // settings and locale
  if (mCJKPrefLangs.Length() == 0) {
    // temp array
    eFontPrefLang tempPrefLangs[kMaxLenPrefLangList];
    uint32_t tempLen = 0;

    // Add the CJK pref fonts from accept languages, the order should be same
    // order. We use gfxFontUtils::GetPrefsFontList to read the list even
    // though it's not actually a list of fonts but of lang codes; the format
    // is the same.
    AutoTArray<nsCString, 5> list;
    gfxFontUtils::GetPrefsFontList("intl.accept_languages", list, true);
    for (const auto& lang : list) {
      eFontPrefLang fpl = GetFontPrefLangFor(lang.get());
      switch (fpl) {
        case eFontPrefLang_Japanese:
        case eFontPrefLang_Korean:
        case eFontPrefLang_ChineseCN:
        case eFontPrefLang_ChineseHK:
        case eFontPrefLang_ChineseTW:
          AppendPrefLang(tempPrefLangs, tempLen, fpl);
          break;
        default:
          break;
      }
    }

    // Try using app's locale
    nsAutoCString localeStr;
    LocaleService::GetInstance()->GetAppLocaleAsBCP47(localeStr);

    {
      Locale locale;
      if (LocaleParser::TryParse(localeStr, locale).isOk() &&
          locale.Canonicalize().isOk()) {
        if (locale.Language().EqualTo("ja")) {
          AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
        } else if (locale.Language().EqualTo("zh")) {
          if (locale.Region().EqualTo("CN")) {
            AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
          } else if (locale.Region().EqualTo("TW")) {
            AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
          } else if (locale.Region().EqualTo("HK")) {
            AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
          }
        } else if (locale.Language().EqualTo("ko")) {
          AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
        }
      }
    }

    // Then add the known CJK prefs in order of system preferred locales
    AutoTArray<nsCString, 5> prefLocales;
    prefLocales.AppendElement("ja"_ns);
    prefLocales.AppendElement("zh-CN"_ns);
    prefLocales.AppendElement("zh-TW"_ns);
    prefLocales.AppendElement("zh-HK"_ns);
    prefLocales.AppendElement("ko"_ns);

    AutoTArray<nsCString, 16> sysLocales;
    AutoTArray<nsCString, 16> negLocales;
    if (NS_SUCCEEDED(
            OSPreferences::GetInstance()->GetSystemLocales(sysLocales))) {
      LocaleService::GetInstance()->NegotiateLanguages(
          sysLocales, prefLocales, ""_ns,
          LocaleService::kLangNegStrategyFiltering, negLocales);
      for (const auto& localeStr : negLocales) {
        Locale locale;
        if (LocaleParser::TryParse(localeStr, locale).isOk() &&
            locale.Canonicalize().isOk()) {
          if (locale.Language().EqualTo("ja")) {
            AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
          } else if (locale.Language().EqualTo("zh")) {
            if (locale.Region().EqualTo("CN")) {
              AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
            } else if (locale.Region().EqualTo("TW")) {
              AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
            } else if (locale.Region().EqualTo("HK")) {
              AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
            }
          } else if (locale.Language().EqualTo("ko")) {
            AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
          }
        }
      }
    }

    // Last resort... set up CJK font prefs in the order listed by the user-
    // configurable ordering pref.
    gfxFontUtils::GetPrefsFontList(kCJKFallbackOrderPref, list);
    for (const auto& item : list) {
      eFontPrefLang fpl = GetFontPrefLangFor(item.get());
      switch (fpl) {
        case eFontPrefLang_Japanese:
        case eFontPrefLang_Korean:
        case eFontPrefLang_ChineseCN:
        case eFontPrefLang_ChineseHK:
        case eFontPrefLang_ChineseTW:
          AppendPrefLang(tempPrefLangs, tempLen, fpl);
          break;
        default:
          break;
      }
    }

    // Truly-last resort... try Chinese font prefs before Japanese because
    // they tend to have more complete character coverage, and therefore less
    // risk of "ransom-note" effects.
    // (If the kCJKFallbackOrderPref was fully populated, as it is by default,
    // this will do nothing as all these values are already present.)
    AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
    AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
    AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
    AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
    AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);

    // copy into the cached array
    for (const auto lang : Span<eFontPrefLang>(tempPrefLangs, tempLen)) {
      mCJKPrefLangs.AppendElement(lang);
    }
  }

  // append in cached CJK langs
  for (const auto lang : mCJKPrefLangs) {
    AppendPrefLang(aPrefLangs, aLen, eFontPrefLang(lang));
  }
}

void gfxPlatformFontList::AppendPrefLang(eFontPrefLang aPrefLangs[],
                                         uint32_t& aLen,
                                         eFontPrefLang aAddLang) {
  if (aLen >= kMaxLenPrefLangList) {
    return;
  }

  // If the lang is already present, just ignore the addition.
  for (const auto lang : Span<eFontPrefLang>(aPrefLangs, aLen)) {
    if (lang == aAddLang) {
      return;
    }
  }

  aPrefLangs[aLen++] = aAddLang;
}

StyleGenericFontFamily gfxPlatformFontList::GetDefaultGeneric(
    eFontPrefLang aLang) {
  if (aLang == eFontPrefLang_Emoji) {
    return StyleGenericFontFamily::MozEmoji;
  }

  AutoLock lock(mLock);

  if (uint32_t(aLang) < std::size(gPrefLangNames)) {
    return mDefaultGenericsLangGroup[uint32_t(aLang)];
  }
  return StyleGenericFontFamily::Serif;
}

FontFamily gfxPlatformFontList::GetDefaultFont(nsPresContext* aPresContext,
                                               const gfxFontStyle* aStyle) {
  AutoLock lock(mLock);
  return GetDefaultFontLocked(aPresContext, aStyle);
}

FontFamily gfxPlatformFontList::GetDefaultFontLocked(
    nsPresContext* aPresContext, const gfxFontStyle* aStyle) {
  FontFamily family = GetDefaultFontForPlatform(aPresContext, aStyle);
  if (!family.IsNull()) {
    return family;
  }
  // Something has gone wrong and we were unable to retrieve a default font
  // from the platform. (Likely the whitelist has blocked all potential
  // default fonts.) As a last resort, we return the first font in our list.
  if (SharedFontList()) {
    MOZ_RELEASE_ASSERT(SharedFontList()->NumFamilies() > 0);
    return FontFamily(SharedFontList()->Families());
  }
  MOZ_RELEASE_ASSERT(mFontFamilies.Count() > 0);
  return FontFamily(mFontFamilies.ConstIter().Data());
}

void gfxPlatformFontList::GetFontFamilyNames(
    nsTArray<nsCString>& aFontFamilyNames) {
  if (SharedFontList()) {
    fontlist::FontList* list = SharedFontList();
    const fontlist::Family* families = list->Families();
    if (families) {
      for (uint32_t i = 0, n = list->NumFamilies(); i < n; i++) {
        const fontlist::Family& family = families[i];
        if (!family.IsHidden()) {
          aFontFamilyNames.AppendElement(family.DisplayName().AsString(list));
        }
      }
    }
  } else {
    for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
      if (!family->IsHidden()) {
        aFontFamilyNames.AppendElement(family->Name());
      }
    }
  }
}

nsAtom* gfxPlatformFontList::GetLangGroup(nsAtom* aLanguage) {
  // map lang ==> langGroup
  nsAtom* langGroup = nullptr;
  if (aLanguage) {
    langGroup = mLangService->GetLanguageGroup(aLanguage);
  }
  if (!langGroup) {
    langGroup = nsGkAtoms::Unicode;
  }
  return langGroup;
}

/* static */ const char* gfxPlatformFontList::GetGenericName(
    StyleGenericFontFamily aGenericType) {
  // type should be standard generic type at this point
  // map generic type to string
  switch (aGenericType) {
    case StyleGenericFontFamily::Serif:
      return "serif";
    case StyleGenericFontFamily::SansSerif:
      return "sans-serif";
    case StyleGenericFontFamily::Monospace:
      return "monospace";
    case StyleGenericFontFamily::Cursive:
      return "cursive";
    case StyleGenericFontFamily::Fantasy:
      return "fantasy";
    case StyleGenericFontFamily::SystemUi:
      return "system-ui";
    case StyleGenericFontFamily::MozEmoji:
      return "-moz-emoji";
    case StyleGenericFontFamily::None:
      break;
  }
  MOZ_ASSERT_UNREACHABLE("Unknown generic");
  return nullptr;
}

void gfxPlatformFontList::InitLoader() {
  GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad);
  mStartIndex = 0;
  mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length();
  memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats));
}

#define FONT_LOADER_MAX_TIMESLICE \
  20  // max time for one pass through RunLoader = 20ms

bool gfxPlatformFontList::LoadFontInfo() {
  AutoLock lock(mLock);
  TimeStamp start = TimeStamp::Now();
  uint32_t i, endIndex = mNumFamilies;
  fontlist::FontList* list = SharedFontList();
  bool loadCmaps =
      !list && (!UsesSystemFallback() ||
                gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback());

  // for each font family, load in various font info
  for (i = mStartIndex; i < endIndex; i++) {
    nsAutoCString key;
    GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key);

    if (list) {
      fontlist::Family* family = list->FindFamily(key);
      if (!family) {
        continue;
      }
      ReadFaceNamesForFamily(family, NeedFullnamePostscriptNames());
    } else {
      // lookup in canonical (i.e. English) family name list
      gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
      if (!familyEntry) {
        continue;
      }

      // read in face names
      familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames(),
                                 mFontInfo);

      // load the cmaps if needed
      if (loadCmaps) {
        familyEntry->ReadAllCMAPs(mFontInfo);
      }
    }

    // Limit the time spent reading fonts in one pass, unless the font-loader
    // delay was set to zero, in which case we run to completion even if it
    // causes some jank.
    if (StaticPrefs::gfx_font_loader_delay() > 0) {
      TimeDuration elapsed = TimeStamp::Now() - start;
      if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE &&
          i + 1 != endIndex) {
        endIndex = i + 1;
        break;
      }
    }
  }

  mStartIndex = endIndex;
  bool done = mStartIndex >= mNumFamilies;

  if (LOG_FONTINIT_ENABLED()) {
    TimeDuration elapsed = TimeStamp::Now() - start;
    LOG_FONTINIT(("(fontinit) fontloader load pass %8.2f ms done %s\n",
                  elapsed.ToMilliseconds(), (done ? "true" : "false")));
  }

  if (done) {
    mOtherFamilyNamesInitialized = true;
    CancelInitOtherFamilyNamesTask();
    mFaceNameListsInitialized = true;
  }

  return done;
}

void gfxPlatformFontList::CleanupLoader() {
  AutoLock lock(mLock);

  mFontFamiliesToLoad.Clear();
  mNumFamilies = 0;
  bool rebuilt = false, forceReflow = false;

  // if had missed face names that are now available, force reflow all
  if (mFaceNamesMissed) {
    rebuilt = std::any_of(mFaceNamesMissed->cbegin(), mFaceNamesMissed->cend(),
                          [&](const auto& key) {
                            mLock.AssertCurrentThreadIn();
                            return FindFaceName(key);
                          });
    if (rebuilt) {
      RebuildLocalFonts();
    }

    mFaceNamesMissed = nullptr;
  }

  if (mOtherNamesMissed) {
    forceReflow = std::any_of(
        mOtherNamesMissed->cbegin(), mOtherNamesMissed->cend(),
        [&](const auto& key) {
          mLock.AssertCurrentThreadIn();
          return FindUnsharedFamily(
              nullptr, key,
              (FindFamiliesFlags::eForceOtherFamilyNamesLoading |
               FindFamiliesFlags::eNoAddToNamesMissedWhenSearching));
        });
    if (forceReflow) {
      gfxPlatform::GlobalReflowFlags flags =
          gfxPlatform::GlobalReflowFlags::FontsChanged;
      ForceGlobalReflowLocked(flags);
    }

    mOtherNamesMissed = nullptr;
  }

  if (LOG_FONTINIT_ENABLED() && mFontInfo) {
    LOG_FONTINIT(
        ("(fontinit) fontloader load thread took %8.2f ms "
         "%d families %d fonts %d cmaps "
         "%d facenames %d othernames %s %s",
         mLoadTime.ToMilliseconds(), mFontInfo->mLoadStats.families,
         mFontInfo->mLoadStats.fonts, mFontInfo->mLoadStats.cmaps,
         mFontInfo->mLoadStats.facenames, mFontInfo->mLoadStats.othernames,
         (rebuilt ? "(userfont sets rebuilt)" : ""),
         (forceReflow ? "(global reflow)" : "")));
  }

  gfxFontInfoLoader::CleanupLoader();
}

void gfxPlatformFontList::ForceGlobalReflow(
    gfxPlatform::GlobalReflowFlags aFlags) {
  if (!NS_IsMainThread()) {
    NS_DispatchToMainThread(NS_NewRunnableFunction(
        "gfxPlatformFontList::ForceGlobalReflow",
        [this, aFlags] { this->ForceGlobalReflow(aFlags); }));
    return;
  }

  if (aFlags & gfxPlatform::GlobalReflowFlags::FontsChanged) {
    AutoLock lock(mLock);
    InitializeCodepointsWithNoFonts();
    if (SharedFontList()) {
      // If we're using a shared local face-name list, this may have changed
      // such that existing font entries held by user font sets are no longer
      // safe to use: ensure they all get flushed.
      RebuildLocalFonts(/*aForgetLocalFaces*/ true);
    }
  }

  gfxPlatform::ForceGlobalReflow(aFlags);
}

void gfxPlatformFontList::ForceGlobalReflowLocked(
    gfxPlatform::GlobalReflowFlags aFlags) {
  if (!NS_IsMainThread()) {
    NS_DispatchToMainThread(NS_NewRunnableFunction(
        "gfxPlatformFontList::ForceGlobalReflow",
        [this, aFlags] { this->ForceGlobalReflow(aFlags); }));
    return;
  }

  if (aFlags & gfxPlatform::GlobalReflowFlags::FontsChanged) {
    InitializeCodepointsWithNoFonts();
    if (SharedFontList()) {
      RebuildLocalFonts(/*aForgetLocalFaces*/ true);
    }
  }

  AutoUnlock unlock(mLock);
  gfxPlatform::ForceGlobalReflow(aFlags);
}

void gfxPlatformFontList::GetPrefsAndStartLoader() {
  // If we're already in shutdown, there's no point in starting this, and it
  // could trigger an assertion if we try to use the Thread Manager too late.
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return;
  }
  uint32_t delay = std::max(1u, StaticPrefs::gfx_font_loader_delay());
  if (NS_IsMainThread()) {
    StartLoader(delay);
  } else {
    NS_DispatchToMainThread(NS_NewRunnableFunction(
        "StartLoader callback", [delay, fontList = this] {
          fontList->Lock();
          fontList->StartLoader(delay);
          fontList->Unlock();
        }));
  }
}

void gfxPlatformFontList::RebuildLocalFonts(bool aForgetLocalFaces) {
  // Make a local copy of the list of font sets we need to process.
  AutoTArray<RefPtr<gfxUserFontSet>, 16> fontSets;
  fontSets.SetCapacity(mUserFontSetList.Count());
  for (auto* fontset : mUserFontSetList) {
    fontSets.AppendElement(fontset);
  }
  // Drop our lock before calling ForgetLocalFaces and RebuildLocalRules
  // for each set, to avoid possible deadlocks.
  AutoUnlock unlock(mLock);
  for (auto fontset : fontSets) {
    if (aForgetLocalFaces) {
      fontset->ForgetLocalFaces();
    }
    fontset->RebuildLocalRules();
  }
}

void gfxPlatformFontList::ClearLangGroupPrefFontsLocked() {
  for (uint32_t i = eFontPrefLang_First;
       i < eFontPrefLang_First + eFontPrefLang_Count; i++) {
    auto& prefFontsLangGroup = mLangGroupPrefFonts[i];
    for (auto& pref : prefFontsLangGroup) {
      pref = nullptr;
    }
  }
  mCJKPrefLangs.Clear();
  mEmojiPrefFont = nullptr;

  // Create a new FontPrefs and replace the existing one.
  mFontPrefs = MakeUnique<FontPrefs>();
}

// Support for memory reporting

// this is also used by subclasses that hold additional font tables
/*static*/
size_t gfxPlatformFontList::SizeOfFontFamilyTableExcludingThis(
    const FontFamilyTable& aTable, MallocSizeOf aMallocSizeOf) {
  return std::accumulate(
      aTable.Keys().cbegin(), aTable.Keys().cend(),
      aTable.ShallowSizeOfExcludingThis(aMallocSizeOf),
      [&](size_t oldValue, const nsACString& key) {
        // We don't count the size of the family here, because this is an
        // *extra* reference to a family that will have already been counted in
        // the main list.
        return oldValue + key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
      });
}

/*static*/
size_t gfxPlatformFontList::SizeOfFontEntryTableExcludingThis(
    const FontEntryTable& aTable, MallocSizeOf aMallocSizeOf) {
  return std::accumulate(
      aTable.Keys().cbegin(), aTable.Keys().cend(),
      aTable.ShallowSizeOfExcludingThis(aMallocSizeOf),
      [&](size_t oldValue, const nsACString& key) {
        // The font itself is counted by its owning family; here we only care
        // about the names stored in the hashtable keys.

        return oldValue + key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
      });
}

void gfxPlatformFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                                 FontListSizes* aSizes) const {
  AutoLock lock(mLock);

  aSizes->mFontListSize +=
      mFontFamilies.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& entry : mFontFamilies) {
    aSizes->mFontListSize +=
        entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
    entry.GetData()->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
  }

  aSizes->mFontListSize +=
      SizeOfFontFamilyTableExcludingThis(mOtherFamilyNames, aMallocSizeOf);

  if (mExtraNames) {
    aSizes->mFontListSize += SizeOfFontEntryTableExcludingThis(
        mExtraNames->mFullnames, aMallocSizeOf);
    aSizes->mFontListSize += SizeOfFontEntryTableExcludingThis(
        mExtraNames->mPostscriptNames, aMallocSizeOf);
  }

  for (uint32_t i = eFontPrefLang_First;
       i < eFontPrefLang_First + eFontPrefLang_Count; i++) {
    auto& prefFontsLangGroup = mLangGroupPrefFonts[i];
    for (const UniquePtr<PrefFontList>& pf : prefFontsLangGroup) {
      if (pf) {
        aSizes->mFontListSize += pf->ShallowSizeOfExcludingThis(aMallocSizeOf);
      }
    }
  }

  for (const auto& bitset : mCodepointsWithNoFonts) {
    aSizes->mFontListSize += bitset.SizeOfExcludingThis(aMallocSizeOf);
  }
  aSizes->mFontListSize +=
      mFontFamiliesToLoad.ShallowSizeOfExcludingThis(aMallocSizeOf);

  aSizes->mFontListSize +=
      mBadUnderlineFamilyNames.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& i : mBadUnderlineFamilyNames) {
    aSizes->mFontListSize += i.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
  }

  aSizes->mFontListSize +=
      mSharedCmaps.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& entry : mSharedCmaps) {
    aSizes->mCharMapsSize += entry.GetKey()->SizeOfIncludingThis(aMallocSizeOf);
  }

  aSizes->mFontListSize +=
      mFontEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& entry : mFontEntries.Values()) {
    if (entry) {
      entry->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
    }
  }

  if (SharedFontList()) {
    aSizes->mFontListSize +=
        SharedFontList()->SizeOfIncludingThis(aMallocSizeOf);
    if (XRE_IsParentProcess()) {
      aSizes->mSharedSize += SharedFontList()->AllocatedShmemSize();
    }
  }
}

void gfxPlatformFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                                 FontListSizes* aSizes) const {
  aSizes->mFontListSize += aMallocSizeOf(this);
  AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}

void gfxPlatformFontList::InitOtherFamilyNamesInternal(
    bool aDeferOtherFamilyNamesLoading) {
  if (mOtherFamilyNamesInitialized) {
    return;
  }

  AutoLock lock(mLock);

  if (aDeferOtherFamilyNamesLoading) {
    TimeStamp start = TimeStamp::Now();
    bool timedOut = false;

    auto list = SharedFontList();
    if (list) {
      // If the gfxFontInfoLoader task is not yet running, kick it off now so
      // that it will load remaining names etc as soon as idle time permits.
      if (mState == stateInitial || mState == stateTimerOnDelay) {
        StartLoader(0);
        timedOut = true;
      }
    } else {
      for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
        family->ReadOtherFamilyNames(this);
        TimeDuration elapsed = TimeStamp::Now() - start;
        if (elapsed.ToMilliseconds() > OTHERNAMES_TIMEOUT) {
          timedOut = true;
          break;
        }
      }
    }

    if (!timedOut) {
      mOtherFamilyNamesInitialized = true;
      CancelInitOtherFamilyNamesTask();
    }
    TimeDuration elapsed = TimeStamp::Now() - start;
    glean::fontlist::initotherfamilynames.AccumulateRawDuration(elapsed);

    if (LOG_FONTINIT_ENABLED()) {
      LOG_FONTINIT(("(fontinit) InitOtherFamilyNames took %8.2f ms %s",
                    elapsed.ToMilliseconds(), (timedOut ? "timeout" : "")));
    }
  } else {
    TimeStamp start = TimeStamp::Now();

    auto list = SharedFontList();
    if (list) {
      for (auto& f : mozilla::Range<fontlist::Family>(list->Families(),
                                                      list->NumFamilies())) {
        ReadFaceNamesForFamily(&f, false);
      }
    } else {
      for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
        family->ReadOtherFamilyNames(this);
      }
    }

    mOtherFamilyNamesInitialized = true;
    CancelInitOtherFamilyNamesTask();

    TimeDuration elapsed = TimeStamp::Now() - start;
    glean::fontlist::initotherfamilynames_no_deferring.AccumulateRawDuration(
        elapsed);

    if (LOG_FONTINIT_ENABLED()) {
      LOG_FONTINIT(
          ("(fontinit) InitOtherFamilyNames without deferring took %8.2f ms",
           elapsed.ToMilliseconds()));
    }
  }
}

void gfxPlatformFontList::CancelInitOtherFamilyNamesTask() {
  if (mPendingOtherFamilyNameTask) {
    mPendingOtherFamilyNameTask->Cancel();
    mPendingOtherFamilyNameTask = nullptr;
  }
  auto list = SharedFontList();
  if (list && XRE_IsParentProcess()) {
    bool forceReflow = false;
    if (!mAliasTable.IsEmpty()) {
      list->SetAliases(mAliasTable);
      mAliasTable.Clear();
      forceReflow = true;
    }
    if (mLocalNameTable.Count()) {
      list->SetLocalNames(mLocalNameTable);
      mLocalNameTable.Clear();
      forceReflow = true;
    }
    // If there's a LoadCmapsRunnable alive, we ignore forceReflow because the
    // runnable will trigger a reflow when it completes, and we don't want to
    // reflow more times than necessary.
    if (forceReflow && !mLoadCmapsRunnable) {
      gfxPlatform::GlobalReflowFlags flags =
          gfxPlatform::GlobalReflowFlags::BroadcastToChildren |
          gfxPlatform::GlobalReflowFlags::FontsChanged;
      gfxPlatform::ForceGlobalReflow(flags);
    }
  }
}

void gfxPlatformFontList::ShareFontListShmBlockToProcess(
    uint32_t aGeneration, uint32_t aIndex, base::ProcessId aPid,
    mozilla::ipc::SharedMemory::Handle* aOut) {
  auto list = SharedFontList();
  if (!list) {
    return;
  }
  if (!aGeneration || list->GetGeneration() == aGeneration) {
    list->ShareShmBlockToProcess(aIndex, aPid, aOut);
  } else {
    *aOut = mozilla::ipc::SharedMemory::NULLHandle();
  }
}

void gfxPlatformFontList::ShareFontListToProcess(
    nsTArray<mozilla::ipc::SharedMemory::Handle>* aBlocks,
    base::ProcessId aPid) {
  auto list = SharedFontList();
  if (list) {
    list->ShareBlocksToProcess(aBlocks, aPid);
  }
}

mozilla::ipc::SharedMemory::Handle gfxPlatformFontList::ShareShmBlockToProcess(
    uint32_t aIndex, base::ProcessId aPid) {
  MOZ_RELEASE_ASSERT(SharedFontList());
  return SharedFontList()->ShareBlockToProcess(aIndex, aPid);
}

void gfxPlatformFontList::ShmBlockAdded(
    uint32_t aGeneration, uint32_t aIndex,
    mozilla::ipc::SharedMemory::Handle aHandle) {
  if (SharedFontList()) {
    AutoLock lock(mLock);
    SharedFontList()->ShmBlockAdded(aGeneration, aIndex, std::move(aHandle));
  }
}

void gfxPlatformFontList::InitializeFamily(uint32_t aGeneration,
                                           uint32_t aFamilyIndex,
                                           bool aLoadCmaps) {
  auto list = SharedFontList();
  MOZ_ASSERT(list);
  if (!list) {
    return;
  }
  if (list->GetGeneration() != aGeneration) {
    return;
  }
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return;
  }
  if (aFamilyIndex >= list->NumFamilies()) {
    return;
  }
  fontlist::Family* family = list->Families() + aFamilyIndex;
  if (!family->IsInitialized() || aLoadCmaps) {
    Unused << InitializeFamily(family, aLoadCmaps);
  }
}

void gfxPlatformFontList::SetCharacterMap(uint32_t aGeneration,
                                          uint32_t aFamilyIndex, bool aAlias,
                                          uint32_t aFaceIndex,
                                          const gfxSparseBitSet& aMap) {
  MOZ_ASSERT(XRE_IsParentProcess());
  auto list = SharedFontList();
  MOZ_ASSERT(list);
  if (!list) {
    return;
  }
  if (list->GetGeneration() != aGeneration) {
    return;
  }
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return;
  }

  const fontlist::Family* family;
  if (aAlias) {
    if (aFamilyIndex >= list->NumAliases()) {
      MOZ_ASSERT(false"AliasFamily index out of range");
      return;
    }
    family = list->AliasFamilies() + aFamilyIndex;
  } else {
    if (aFamilyIndex >= list->NumFamilies()) {
      MOZ_ASSERT(false"Family index out of range");
      return;
    }
    family = list->Families() + aFamilyIndex;
  }

  if (aFaceIndex >= family->NumFaces()) {
    MOZ_ASSERT(false"Face index out of range");
    return;
  }

  if (auto* face =
          family->Faces(list)[aFaceIndex].ToPtr<fontlist::Face>(list)) {
    face->mCharacterMap = GetShmemCharMap(&aMap);
  }
}

void gfxPlatformFontList::SetupFamilyCharMap(uint32_t aGeneration,
                                             uint32_t aIndex, bool aAlias) {
  MOZ_ASSERT(XRE_IsParentProcess());
  auto list = SharedFontList();
  MOZ_ASSERT(list);
  if (!list) {
    return;
  }
  if (list->GetGeneration() != aGeneration) {
    return;
  }
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return;
  }

  if (aAlias) {
    if (aIndex >= list->NumAliases()) {
      MOZ_ASSERT(false"AliasFamily index out of range");
      return;
    }
    list->AliasFamilies()[aIndex].SetupFamilyCharMap(list);
    return;
  }

  if (aIndex >= list->NumFamilies()) {
    MOZ_ASSERT(false"Family index out of range");
    return;
  }
  list->Families()[aIndex].SetupFamilyCharMap(list);
}

bool gfxPlatformFontList::InitOtherFamilyNames(uint32_t aGeneration,
                                               bool aDefer) {
  auto list = SharedFontList();
  MOZ_ASSERT(list);
  if (!list) {
    return false;
  }
  if (list->GetGeneration() != aGeneration) {
    return false;
  }
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return false;
  }
  return InitOtherFamilyNames(aDefer);
}

uint32_t gfxPlatformFontList::GetGeneration() const {
  return SharedFontList() ? SharedFontList()->GetGeneration() : 0;
}

gfxPlatformFontList::FontPrefs::FontPrefs() {
  // This must be created on the main thread, so that we can safely use the
  // Preferences service. Once created, it can be read from any thread.
  MOZ_ASSERT(NS_IsMainThread());
  Init();
}

void gfxPlatformFontList::FontPrefs::Init() {
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
    return;
  }
  nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch();
  if (!prefRootBranch) {
    return;
  }
  nsTArray<nsCString> prefNames;
  if (NS_SUCCEEDED(prefRootBranch->GetChildList(kNamePrefix, prefNames))) {
    for (auto& prefName : prefNames) {
      nsAutoCString value;
      if (NS_SUCCEEDED(Preferences::GetCString(prefName.get(), value))) {
        nsAutoCString pref(Substring(prefName, sizeof(kNamePrefix) - 1));
        mFontName.InsertOrUpdate(pref, value);
      }
    }
  }
  if (NS_SUCCEEDED(prefRootBranch->GetChildList(kNameListPrefix, prefNames))) {
    for (auto& prefName : prefNames) {
      nsAutoCString value;
      if (NS_SUCCEEDED(Preferences::GetCString(prefName.get(), value))) {
        nsAutoCString pref(Substring(prefName, sizeof(kNameListPrefix) - 1));
        mFontNameList.InsertOrUpdate(pref, value);
      }
    }
  }
  mEmojiHasUserValue = Preferences::HasUserValue("font.name-list.emoji");
}

bool gfxPlatformFontList::FontPrefs::LookupName(const nsACString& aPref,
                                                nsACString& aValue) const {
  if (const auto& value = mFontName.Lookup(aPref)) {
    aValue = *value;
    return true;
  }
  return false;
}

bool gfxPlatformFontList::FontPrefs::LookupNameList(const nsACString& aPref,
                                                    nsACString& aValue) const {
  if (const auto& value = mFontNameList.Lookup(aPref)) {
    aValue = *value;
    return true;
  }
  return false;
}

bool gfxPlatformFontList::IsKnownIconFontFamily(
    const nsAtom* aFamilyName) const {
  nsAtomCString fam(aFamilyName);
  ToLowerCase(fam);
  return mIconFontsSet.Contains(fam);
}

#undef LOG
#undef LOG_ENABLED

Messung V0.5 in Prozent
C=91 H=91 G=90

¤ Dauer der Verarbeitung: 0.45 Sekunden  (vorverarbeitet am  2026-04-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.