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() : ""),
             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;
--> --------------------

--> maximum size reached

--> --------------------

91%


¤ Dauer der Verarbeitung: 0.10 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Normalansicht

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 ist noch experimentell.