diff options
Diffstat (limited to 'gfx/thebes/gfxPlatformFontList.cpp')
-rw-r--r-- | gfx/thebes/gfxPlatformFontList.cpp | 3174 |
1 files changed, 3174 insertions, 0 deletions
diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp new file mode 100644 index 0000000000..1131ecb513 --- /dev/null +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -0,0 +1,3174 @@ +/* -*- 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/Telemetry.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/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(MOZ_ARRAY_LENGTH(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::ForceGlobalReflow(gfxPlatform::NeedsReframe::No); + } + 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(ArrayLength(gPrefLangNames)); + for (uint32_t i = 0; i < ArrayLength(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() { + // We take the lock here because it's possible the InitFontList thread is + // still running, in which case we need to wait for it to finish; this will + // block until the lock becomes available, ensuring we don't destroy things + // the initialization thread is using. + 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); +} + +/* 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) { + if (entry.GetData()->IsHidden()) { + // Hidden system fonts are exempt from whitelisting, but don't count + // towards determining whether we "kept" any (user-visible) fonts + accepted.AppendElement(entry.GetData()); + continue; + } + 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 (f.mVisibility == FontVisibility::Hidden || + 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 char* const 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); + MOZ_ASSERT(nsCaseInsensitiveUTF8StringComparator(a, b, aLen, bLen) < 0, + "incorrectly sorted font family list!"); + 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. + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::Yes, + gfxPlatform::BroadcastToChildren::No); + + 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::FontListChanged() { + MOZ_ASSERT(!XRE_IsParentProcess()); + 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); + } + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::Yes); +} + +void gfxPlatformFontList::GenerateFontListKey(const nsACString& aKeyName, + nsACString& aResult) { + aResult = aKeyName; + ToLowerCase(aResult); +} + +// 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_AtStartup() > 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); + + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITFACENAMELISTS, start, + end); + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + 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::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; + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::No); + } + } +} + +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; + int32_t intElapsed = + int32_t(first ? elapsed.ToMilliseconds() : elapsed.ToMicroseconds()); + Telemetry::Accumulate((first ? Telemetry::SYSTEM_FONT_FALLBACK_FIRST + : Telemetry::SYSTEM_FONT_FALLBACK), + intElapsed); + first = false; + + // track the script for which fallback occurred (incremented one make it + // 1-based) + Telemetry::Accumulate(Telemetry::SYSTEM_FONT_FALLBACK_SCRIPT, + int(aRunScript) + 1); + + 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 somewhere 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 : public CancelableRunnable { + class WillShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit WillShutdownObserver(LoadCmapsRunnable* aRunnable) + : mRunnable(aRunnable) {} + + void Remove() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + mRunnable = nullptr; + } + + protected: + virtual ~WillShutdownObserver() = default; + + LoadCmapsRunnable* mRunnable; + }; + + public: + explicit LoadCmapsRunnable(uint32_t aGeneration, uint32_t aFamilyIndex) + : CancelableRunnable("gfxPlatformFontList::LoadCmapsRunnable"), + mGeneration(aGeneration), + mStartIndex(aFamilyIndex), + mIndex(aFamilyIndex) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + mObserver = new WillShutdownObserver(this); + obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + } + } + + virtual ~LoadCmapsRunnable() { + if (mObserver) { + mObserver->Remove(); + } + } + + // 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; + } + } + + nsresult Cancel() override { + mIsCanceled = true; + return NS_OK; + } + + 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(); + // Skip any families that are already initialized. + while (mIndex < numFamilies && families[mIndex].IsFullyInitialized()) { + ++mIndex; + } + // Fully process one family, and advance index. + if (mIndex < numFamilies) { + Unused << pfl->InitializeFamily(&families[mIndex], true); + ++mIndex; + } + // If there are more families to initialize, post ourselves back to the + // idle queue to handle the next one; otherwise we're finished and we need + // to notify content processes to update their rendering. + if (mIndex < numFamilies) { + RefPtr<CancelableRunnable> task = this; + NS_DispatchToMainThreadQueue(task.forget(), 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; + bool mIsCanceled = false; + + RefPtr<WillShutdownObserver> mObserver; +}; + +NS_IMPL_ISUPPORTS(LoadCmapsRunnable::WillShutdownObserver, nsIObserver) + +NS_IMETHODIMP +LoadCmapsRunnable::WillShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { + if (mRunnable) { + mRunnable->Cancel(); + } + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +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. + static_cast<LoadCmapsRunnable*>(mLoadCmapsRunnable.get()) + ->MaybeResetIndex(aStartIndex); + return; + } + mLoadCmapsRunnable = new LoadCmapsRunnable(aGeneration, aStartIndex); + RefPtr<CancelableRunnable> task = mLoadCmapsRunnable; + NS_DispatchToMainThreadQueue(task.forget(), 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::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 (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()), + mCharMap(), + 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([[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 (!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(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 < ArrayLength(gPrefLangToLangGroups) + ? gPrefLangToLangGroups[aIndex] + : nsGkAtoms::Unicode; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(const char* aLang) { + if (!aLang || !aLang[0]) { + return eFontPrefLang_Others; + } + for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); ++i) { + if (!nsCRT::strcasecmp(gPrefLangNames[i], aLang)) { + 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) < ArrayLength(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) < ArrayLength(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_AtStartup() > 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) { + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::No); + } + + 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::ForceGlobalReflowLocked( + gfxPlatform::NeedsReframe aNeedsReframe, + gfxPlatform::BroadcastToChildren aBroadcastToChildren) { + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "gfxPlatformFontList::ForceGlobalReflowLocked", + [aNeedsReframe, aBroadcastToChildren] { + gfxPlatform::ForceGlobalReflow(aNeedsReframe, aBroadcastToChildren); + })); + return; + } + + AutoUnlock unlock(mLock); + gfxPlatform::ForceGlobalReflow(aNeedsReframe, aBroadcastToChildren); +} + +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_AtStartup()); + 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) { + for (auto* fontset : mUserFontSetList) { + 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(); + } + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITOTHERFAMILYNAMES, + start, end); + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + 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(); + + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta( + Telemetry::FONTLIST_INITOTHERFAMILYNAMES_NO_DEFERRING, start, end); + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + 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 (forceReflow) { + dom::ContentParent::BroadcastFontListChanged(); + } + } +} + +void gfxPlatformFontList::ShareFontListShmBlockToProcess( + uint32_t aGeneration, uint32_t aIndex, base::ProcessId aPid, + base::SharedMemoryHandle* aOut) { + auto list = SharedFontList(); + if (!list) { + return; + } + if (!aGeneration || list->GetGeneration() == aGeneration) { + list->ShareShmBlockToProcess(aIndex, aPid, aOut); + } else { + *aOut = base::SharedMemory::NULLHandle(); + } +} + +void gfxPlatformFontList::ShareFontListToProcess( + nsTArray<base::SharedMemoryHandle>* aBlocks, base::ProcessId aPid) { + auto list = SharedFontList(); + if (list) { + list->ShareBlocksToProcess(aBlocks, aPid); + } +} + +base::SharedMemoryHandle 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, + base::SharedMemoryHandle 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, + const fontlist::Pointer& aFacePtr, + 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; + } + auto* face = aFacePtr.ToPtr<fontlist::Face>(list); + if (face) { + face->mCharacterMap = GetShmemCharMap(&aMap); + } +} + +void gfxPlatformFontList::SetupFamilyCharMap( + uint32_t aGeneration, const fontlist::Pointer& aFamilyPtr) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + + // aFamilyPtr was passed from a content process which may not be trusted, + // so we cannot assume it is valid or safe to use. If the Pointer value is + // bad, we must not crash or do anything bad, just bail out. + // (In general, if the child process was trying to use an invalid pointer it + // should have hit the MOZ_DIAGNOSTIC_ASSERT in FontList::ToSharedPointer + // rather than passing a null or bad pointer to the parent.) + + auto* family = aFamilyPtr.ToPtr<fontlist::Family>(list); + if (!family) { + // Unable to resolve to a native pointer (or it was null). + NS_WARNING("unexpected null Family pointer"); + return; + } + + // Validate the pointer before trying to use it: check that it points to a + // correctly-aligned offset within the Families() or AliasFamilies() array. + // We just assert (in debug builds only) on failure, and return safely. + // A misaligned pointer here would indicate a buggy (or compromised) child + // process, but crashing the parent would be unnecessary and does not yield + // any useful insight. + if (family >= list->Families() && + family < list->Families() + list->NumFamilies()) { + size_t offset = (char*)family - (char*)list->Families(); + if (offset % sizeof(fontlist::Family) != 0) { + MOZ_ASSERT(false, "misaligned Family pointer"); + return; + } + } else if (family >= list->AliasFamilies() && + family < list->AliasFamilies() + list->NumAliases()) { + size_t offset = (char*)family - (char*)list->AliasFamilies(); + if (offset % sizeof(fontlist::Family) != 0) { + MOZ_ASSERT(false, "misaligned Family pointer"); + return; + } + } else { + MOZ_ASSERT(false, "not a valid Family or AliasFamily pointer"); + return; + } + + family->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() { + 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 |