/* -*- 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/dom/Document.h" #include "mozilla/gfx/2D.h" #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/ResultExtensions.h" #include "mozilla/TextUtils.h" #include "mozilla/Unused.h" #include "base/eintr_wrapper.h" #include #include 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(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(); } mLangService = nsLanguageAtomService::GetService(); LoadBadUnderlineList(); LoadIconFontOverrideList(); mFontPrefs = MakeUnique(); 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 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() { // Note that gfxPlatformFontList::Shutdown() ensures that the init-font-list // thread is finished before we come here. AutoLock lock(mLock); // We can't just do mSharedCmaps.Clear() here because removing each item from // the table would drop its last reference, and its Release() method would // then call back to MaybeRemoveCmap to search for it, which we can't do // while in the middle of clearing the table. // So we first clear the "shared" flag in each entry, so Release() won't try // to re-find them in the table. for (auto iter = mSharedCmaps.ConstIter(); !iter.Done(); iter.Next()) { iter.Get()->mCharMap->ClearSharedFlag(); } mSharedCmaps.Clear(); ClearLangGroupPrefFontsLocked(); NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer"); Preferences::UnregisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs); nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->RemoveObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC); } if (XRE_IsParentProcess()) { Preferences::UnregisterCallback(FontWhitelistPrefChanged, kFontSystemWhitelistPref); } NS_RELEASE(gFontListPrefObserver); } void gfxPlatformFontList::GetMissingFonts(nsCString& aMissingFonts) { AutoLock lock(mLock); auto fontLists = GetFilteredPlatformFontLists(); if (!fontLists.Length()) { aMissingFonts.Append("No font list available for this device."); } for (unsigned int i = 0; i < fontLists.Length(); i++) { for (unsigned int j = 0; j < fontLists[i].second; j++) { nsCString key(fontLists[i].first[j]); GenerateFontListKey(key); if (SharedFontList()) { fontlist::Family* family = SharedFontList()->FindFamily(key); if (!family) { aMissingFonts.Append(fontLists[i].first[j]); aMissingFonts.Append("|"); } } else { gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); if (!familyEntry) { familyEntry = mOtherFamilyNames.GetWeak(key); } if (!familyEntry) { aMissingFonts.Append(fontLists[i].first[j]); aMissingFonts.Append("|"); } } } } } /* 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 familyNamesWhitelist; for (uint32_t i = 0; i < numFonts; i++) { nsAutoCString key; ToLowerCase(mEnabledFontsList[i], key); familyNamesWhitelist.Insert(key); } AutoTArray, 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& aFamilies) { mLock.AssertCurrentThreadIn(); if (!mFontFamilyWhitelistActive) { return; } nsTHashSet familyNamesWhitelist; for (const auto& item : mEnabledFontsList) { nsAutoCString key; ToLowerCase(item, key); familyNamesWhitelist.Insert(key); } AutoTArray 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); if (nsCaseInsensitiveUTF8StringComparator(a, b, aLen, bLen) >= 0) { MOZ_CRASH_UNSAFE_PRINTF("incorrectly sorted font family list: %s >= %s", a, b); } a = b; aLen = bLen; } #endif } bool gfxPlatformFontList::AddWithLegacyFamilyName(const nsACString& aLegacyName, gfxFontEntry* aFontEntry, FontVisibility aVisibility) { mLock.AssertCurrentThreadIn(); bool added = false; nsAutoCString key; ToLowerCase(aLegacyName, key); mOtherFamilyNames .LookupOrInsertWith(key, [&] { RefPtr 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 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); } void gfxPlatformFontList::GenerateFontListKey(nsACString& aKeyName) { ToLowerCase(aKeyName); } // Used if a stylo thread wants to trigger InitOtherFamilyNames in the main // process: we can't do IPC from the stylo thread so we post this to the main // thread instead. class InitOtherFamilyNamesForStylo : public mozilla::Runnable { public: explicit InitOtherFamilyNamesForStylo(bool aDeferOtherFamilyNamesLoading) : Runnable("gfxPlatformFontList::InitOtherFamilyNamesForStylo"), mDefer(aDeferOtherFamilyNamesLoading) {} NS_IMETHOD Run() override { auto pfl = gfxPlatformFontList::PlatformFontList(); auto list = pfl->SharedFontList(); if (!list) { return NS_OK; } bool initialized = false; dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames( list->GetGeneration(), mDefer, &initialized); pfl->mOtherFamilyNamesInitialized.compareExchange(false, initialized); return NS_OK; } private: bool mDefer; }; #define OTHERNAMES_TIMEOUT 200 bool gfxPlatformFontList::InitOtherFamilyNames( bool aDeferOtherFamilyNamesLoading) { if (mOtherFamilyNamesInitialized) { return true; } if (SharedFontList() && !XRE_IsParentProcess()) { if (NS_IsMainThread()) { bool initialized; dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames( SharedFontList()->GetGeneration(), aDeferOtherFamilyNamesLoading, &initialized); mOtherFamilyNamesInitialized.compareExchange(false, initialized); } else { NS_DispatchToMainThread( new InitOtherFamilyNamesForStylo(aDeferOtherFamilyNamesLoading)); } return mOtherFamilyNamesInitialized; } // If the font loader delay has been set to zero, we don't defer loading // additional family names (regardless of the aDefer... parameter), as we // take this to mean availability of font info is to be prioritized over // potential startup perf or main-thread jank. // (This is used so we can reliably run reftests that depend on localized // font-family names being available.) if (aDeferOtherFamilyNamesLoading && StaticPrefs::gfx_font_loader_delay() > 0) { if (!mPendingOtherFamilyNameTask) { RefPtr 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& 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>(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(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& 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& 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>& 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 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 candidate = CommonFontFallback(aPresContext, aCh, aNextCh, aRunScript, aPresentation, aStyle, fallbackFamily); RefPtr 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(script), (font ? font->GetFontEntry()->Name().get() : ""), int32_t(elapsed.ToMicroseconds()), cmapCount)); } // no match? add to set of non-matching codepoints if (!font) { mCodepointsWithNoFonts[level].set(aCh); } else { *aVisibility = fallbackFamily.mShared ? fallbackFamily.mShared->Visibility() : fallbackFamily.mUnshared->Visibility(); if (aCh == 0xFFFD) { mReplacementCharFallbackFamily[level] = fallbackFamily; } } // track system fallback time static bool first = true; 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 gfxPlatformFontList::CommonFontFallback( nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh, Script aRunScript, eFontPresentation aPresentation, const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) { AutoTArray 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 candidateFont; FontFamily candidateFamily; auto check = [&](gfxFontEntry* aFontEntry, FontFamily aFamily) -> already_AddRefed { RefPtr 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 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 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 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 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 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& family : mFontFamilies.Values()) { if (!IsVisibleToCSS(*family, level)) { continue; } // evaluate all fonts in this family for a match family->FindFontForChar(&data); if (data.mMatchDistance == 0.0) { // no better style match is possible, so stop searching break; } } aCmapCount = data.mCmapsTested; if (data.mBestMatch) { aMatchedFamily = FontFamily(data.mMatchedFamily); return data.mBestMatch->FindOrMakeFont(aMatchStyle); } } return nullptr; } class StartCmapLoadingRunnable : public mozilla::Runnable { public: explicit StartCmapLoadingRunnable(uint32_t aStartIndex) : Runnable("gfxPlatformFontList::StartCmapLoadingRunnable"), mStartIndex(aStartIndex) {} NS_IMETHOD Run() override { auto* pfl = gfxPlatformFontList::PlatformFontList(); auto* list = pfl->SharedFontList(); if (!list) { return NS_OK; } if (mStartIndex >= list->NumFamilies()) { return NS_OK; } if (XRE_IsParentProcess()) { pfl->StartCmapLoading(list->GetGeneration(), mStartIndex); } else { dom::ContentChild::GetSingleton()->SendStartCmapLoading( list->GetGeneration(), mStartIndex); } return NS_OK; } private: uint32_t mStartIndex; }; void gfxPlatformFontList::StartCmapLoadingFromFamily(uint32_t aStartIndex) { AutoLock lock(mLock); if (aStartIndex >= mStartedLoadingCmapsFrom) { // We already initiated cmap-loading from here or earlier in the list; // no need to do it again here. return; } mStartedLoadingCmapsFrom = aStartIndex; // If we're already on the main thread, don't bother dispatching a runnable // here to kick off the loading process, just do it directly. if (NS_IsMainThread()) { auto* list = SharedFontList(); if (XRE_IsParentProcess()) { StartCmapLoading(list->GetGeneration(), aStartIndex); } else { dom::ContentChild::GetSingleton()->SendStartCmapLoading( list->GetGeneration(), aStartIndex); } } else { NS_DispatchToMainThread(new StartCmapLoadingRunnable(aStartIndex)); } } class LoadCmapsRunnable : public CancelableRunnable { class WillShutdownObserver : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER explicit WillShutdownObserver(LoadCmapsRunnable* aRunnable) : mRunnable(aRunnable) {} void Remove() { nsCOMPtr 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 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 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 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(mLoadCmapsRunnable.get()) ->MaybeResetIndex(aStartIndex); return; } mLoadCmapsRunnable = new LoadCmapsRunnable(aGeneration, aStartIndex); RefPtr 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::FindAndAddFamilies( nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, const nsACString& aFamily, nsTArray* aOutput, FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, gfxFloat aDevToCssSize) { AutoLock lock(mLock); #ifdef DEBUG auto initialLength = aOutput->Length(); #endif bool didFind = FindAndAddFamiliesLocked(aPresContext, aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage, aDevToCssSize); #ifdef DEBUG auto finalLength = aOutput->Length(); // Validate the expectation that the output-array grows if we return true, // or remains the same (probably empty) if we return false. MOZ_ASSERT_IF(didFind, finalLength > initialLength); MOZ_ASSERT_IF(!didFind, finalLength == initialLength); #endif return didFind; } bool gfxPlatformFontList::FindAndAddFamiliesLocked( nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, const nsACString& aFamily, nsTArray* aOutput, FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, gfxFloat aDevToCssSize) { nsAutoCString key; GenerateFontListKey(aFamily, key); bool allowHidden = bool(aFlags & FindFamiliesFlags::eSearchHiddenFamilies); FontVisibility visibilityLevel = aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; // If this font lookup is the result of resolving a CSS generic (not a direct // font-family request by the page), and RFP settings allow generics to be // unrestricted, bump the effective visibility level applied here so as to // allow user-installed fonts to be used. if (visibilityLevel < FontVisibility::User && aGeneric != StyleGenericFontFamily::None && !aPresContext->Document()->ShouldResistFingerprinting( RFPTarget::FontVisibilityRestrictGenerics)) { visibilityLevel = FontVisibility::User; } if (SharedFontList()) { fontlist::Family* family = SharedFontList()->FindFamily(key); // If not found, and other family names have not yet been initialized, // initialize the rest of the list and try again. This is done lazily // since reading name table entries is expensive. // Although ASCII localized family names are possible they don't occur // in practice, so avoid pulling in names at startup. if (!family && !mOtherFamilyNamesInitialized) { bool triggerLoading = true; bool mayDefer = !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading); if (IsAscii(key)) { // If `key` is an ASCII name, only trigger loading if it includes a // space, and the "base" name (up to the last space) exists as a known // family, so that this might be a legacy styled-family name. const char* data = key.BeginReading(); int32_t index = key.Length(); while (--index > 0) { if (data[index] == ' ') { break; } } if (index <= 0 || !SharedFontList()->FindFamily(nsAutoCString(key.get(), index))) { triggerLoading = false; } } if (triggerLoading) { if (InitOtherFamilyNames(mayDefer)) { family = SharedFontList()->FindFamily(key); } } if (!family && !mOtherFamilyNamesInitialized && !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) { AddToMissedNames(key); } } // Check whether the family we found is actually allowed to be looked up, // according to current font-visibility prefs. if (family) { bool visible = IsVisibleToCSS(*family, visibilityLevel); if (visible || (allowHidden && family->IsHidden())) { aOutput->AppendElement(FamilyAndGeneric(family, aGeneric)); return true; } if (aPresContext) { aPresContext->ReportBlockedFontFamily(*family); } } return false; } NS_ASSERTION(mFontFamilies.Count() != 0, "system font list was not initialized correctly"); auto isBlockedByVisibilityLevel = [=](gfxFontFamily* aFamily) -> bool { bool visible = IsVisibleToCSS(*aFamily, visibilityLevel); if (visible || (allowHidden && aFamily->IsHidden())) { return false; } if (aPresContext) { aPresContext->ReportBlockedFontFamily(*aFamily); } return true; }; // lookup in canonical (i.e. English) family name list gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); if (familyEntry) { if (isBlockedByVisibilityLevel(familyEntry)) { return false; } } // if not found, lookup in other family names list (mostly localized names) if (!familyEntry) { familyEntry = mOtherFamilyNames.GetWeak(key); } if (familyEntry) { if (isBlockedByVisibilityLevel(familyEntry)) { return false; } } // if still not found and other family names not yet fully initialized, // initialize the rest of the list and try again. this is done lazily // since reading name table entries is expensive. // although ASCII localized family names are possible they don't occur // in practice so avoid pulling in names at startup if (!familyEntry && !mOtherFamilyNamesInitialized && !IsAscii(aFamily)) { InitOtherFamilyNames( !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading)); familyEntry = mOtherFamilyNames.GetWeak(key); if (!familyEntry && !mOtherFamilyNamesInitialized && !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) { // localized family names load timed out, add name to list of // names to check after localized names are loaded AddToMissedNames(key); } if (familyEntry) { if (isBlockedByVisibilityLevel(familyEntry)) { return false; } } } familyEntry = CheckFamily(familyEntry); // If we failed to find the requested family, check for a space in the // name; if found, and if the "base" name (up to the last space) exists // as a family, then this might be a legacy GDI-style family name for // an additional weight/width. Try searching the faces of the base family // and create any corresponding legacy families. if (!familyEntry && !(aFlags & FindFamiliesFlags::eNoSearchForLegacyFamilyNames)) { // We don't have nsAString::RFindChar, so look for a space manually const char* data = aFamily.BeginReading(); int32_t index = aFamily.Length(); while (--index > 0) { if (data[index] == ' ') { break; } } if (index > 0) { gfxFontFamily* base = FindUnsharedFamily(aPresContext, Substring(aFamily, 0, index), FindFamiliesFlags::eNoSearchForLegacyFamilyNames); // If we found the "base" family name, and if it has members with // legacy names, this will add corresponding font-family entries to // the mOtherFamilyNames list; then retry the legacy-family search. if (base && base->CheckForLegacyFamilyNames(this)) { familyEntry = mOtherFamilyNames.GetWeak(key); } if (familyEntry) { if (isBlockedByVisibilityLevel(familyEntry)) { return false; } } } } if (familyEntry) { aOutput->AppendElement(FamilyAndGeneric(familyEntry, aGeneric)); return true; } return false; } void gfxPlatformFontList::AddToMissedNames(const nsCString& aKey) { if (!mOtherNamesMissed) { mOtherNamesMissed = MakeUnique>(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 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 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(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 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& 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 names; if (mFontPrefs->LookupNameList(PrefName(aGenericFamily, aLangGroup), value)) { gfxFontUtils::ParseFontList(value, names); } for (const nsCString& name : names) { FontFamily family = FindFamily(nullptr, name); if (!family.IsNull()) { return FamilyAndGeneric(family); } } return FamilyAndGeneric(); } ShmemCharMapHashEntry::ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap) : mList(gfxPlatformFontList::PlatformFontList()->SharedFontList()), mHash(aCharMap->GetChecksum()) { size_t len = SharedBitSet::RequiredSize(*aCharMap); mCharMap = mList->Alloc(len); SharedBitSet::Create(mCharMap.ToPtr(mList, len), len, *aCharMap); } fontlist::Pointer gfxPlatformFontList::GetShmemCharMapLocked( const gfxSparseBitSet* aCmap) { auto* entry = mShmemCharMaps.GetEntry(aCmap); if (!entry) { entry = mShmemCharMaps.PutEntry(aCmap); } return entry->GetCharMap(); } // Lookup aCmap in the shared cmap set, adding if not already present. // This is the only way for a reference to a gfxCharacterMap to be acquired // by another thread than its original creator. already_AddRefed 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(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& 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 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 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& aGenericNameFamilies, nsAtom* aLangGroup, PrefFontList* aGenericFamilies) { // lookup and add platform fonts uniquely for (const nsCString& genericFamily : aGenericNameFamilies) { AutoTArray 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(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& 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 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 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 sysLocales; AutoTArray 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(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(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& 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& family : mFontFamilies.Values()) { if (!family->IsHidden()) { aFontFamilyNames.AppendElement(family->Name()); } } } } nsAtom* gfxPlatformFontList::GetLangGroup(nsAtom* aLanguage) { // map lang ==> langGroup nsAtom* langGroup = nullptr; if (aLanguage) { langGroup = mLangService->GetLanguageGroup(aLanguage); } if (!langGroup) { langGroup = nsGkAtoms::Unicode; } return langGroup; } /* static */ const char* gfxPlatformFontList::GetGenericName( StyleGenericFontFamily aGenericType) { // type should be standard generic type at this point // map generic type to string switch (aGenericType) { case StyleGenericFontFamily::Serif: return "serif"; case StyleGenericFontFamily::SansSerif: return "sans-serif"; case StyleGenericFontFamily::Monospace: return "monospace"; case StyleGenericFontFamily::Cursive: return "cursive"; case StyleGenericFontFamily::Fantasy: return "fantasy"; case StyleGenericFontFamily::SystemUi: return "system-ui"; case StyleGenericFontFamily::MozEmoji: return "-moz-emoji"; case StyleGenericFontFamily::None: break; } MOZ_ASSERT_UNREACHABLE("Unknown generic"); return nullptr; } void gfxPlatformFontList::InitLoader() { GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad); mStartIndex = 0; mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length(); memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats)); } #define FONT_LOADER_MAX_TIMESLICE \ 20 // max time for one pass through RunLoader = 20ms bool gfxPlatformFontList::LoadFontInfo() { AutoLock lock(mLock); TimeStamp start = TimeStamp::Now(); uint32_t i, endIndex = mNumFamilies; fontlist::FontList* list = SharedFontList(); bool loadCmaps = !list && (!UsesSystemFallback() || gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback()); // for each font family, load in various font info for (i = mStartIndex; i < endIndex; i++) { nsAutoCString key; GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key); if (list) { fontlist::Family* family = list->FindFamily(key); if (!family) { continue; } ReadFaceNamesForFamily(family, NeedFullnamePostscriptNames()); } else { // lookup in canonical (i.e. English) family name list gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); if (!familyEntry) { continue; } // read in face names familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames(), mFontInfo); // load the cmaps if needed if (loadCmaps) { familyEntry->ReadAllCMAPs(mFontInfo); } } // Limit the time spent reading fonts in one pass, unless the font-loader // delay was set to zero, in which case we run to completion even if it // causes some jank. if (StaticPrefs::gfx_font_loader_delay() > 0) { TimeDuration elapsed = TimeStamp::Now() - start; if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE && i + 1 != endIndex) { endIndex = i + 1; break; } } } mStartIndex = endIndex; bool done = mStartIndex >= mNumFamilies; if (LOG_FONTINIT_ENABLED()) { TimeDuration elapsed = TimeStamp::Now() - start; LOG_FONTINIT(("(fontinit) fontloader load pass %8.2f ms done %s\n", elapsed.ToMilliseconds(), (done ? "true" : "false"))); } if (done) { mOtherFamilyNamesInitialized = true; CancelInitOtherFamilyNamesTask(); mFaceNameListsInitialized = true; } return done; } void gfxPlatformFontList::CleanupLoader() { AutoLock lock(mLock); mFontFamiliesToLoad.Clear(); mNumFamilies = 0; bool rebuilt = false, forceReflow = false; // if had missed face names that are now available, force reflow all if (mFaceNamesMissed) { rebuilt = std::any_of(mFaceNamesMissed->cbegin(), mFaceNamesMissed->cend(), [&](const auto& key) { mLock.AssertCurrentThreadIn(); return FindFaceName(key); }); if (rebuilt) { RebuildLocalFonts(); } mFaceNamesMissed = nullptr; } if (mOtherNamesMissed) { forceReflow = std::any_of( mOtherNamesMissed->cbegin(), mOtherNamesMissed->cend(), [&](const auto& key) { mLock.AssertCurrentThreadIn(); return FindUnsharedFamily( nullptr, key, (FindFamiliesFlags::eForceOtherFamilyNamesLoading | FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)); }); if (forceReflow) { 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()); 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(); } // 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& 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& 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(list->Families(), list->NumFamilies())) { ReadFaceNamesForFamily(&f, false); } } else { for (const RefPtr& 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* 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, uint32_t aFamilyIndex, bool aAlias, uint32_t aFaceIndex, const gfxSparseBitSet& aMap) { MOZ_ASSERT(XRE_IsParentProcess()); auto list = SharedFontList(); MOZ_ASSERT(list); if (!list) { return; } if (list->GetGeneration() != aGeneration) { return; } if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { return; } const fontlist::Family* family; if (aAlias) { if (aFamilyIndex >= list->NumAliases()) { MOZ_ASSERT(false, "AliasFamily index out of range"); return; } family = list->AliasFamilies() + aFamilyIndex; } else { if (aFamilyIndex >= list->NumFamilies()) { MOZ_ASSERT(false, "Family index out of range"); return; } family = list->Families() + aFamilyIndex; } if (aFaceIndex >= family->NumFaces()) { MOZ_ASSERT(false, "Face index out of range"); return; } if (auto* face = family->Faces(list)[aFaceIndex].ToPtr(list)) { face->mCharacterMap = GetShmemCharMap(&aMap); } } void gfxPlatformFontList::SetupFamilyCharMap(uint32_t aGeneration, uint32_t aIndex, bool aAlias) { MOZ_ASSERT(XRE_IsParentProcess()); auto list = SharedFontList(); MOZ_ASSERT(list); if (!list) { return; } if (list->GetGeneration() != aGeneration) { return; } if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { return; } if (aAlias) { if (aIndex >= list->NumAliases()) { MOZ_ASSERT(false, "AliasFamily index out of range"); return; } list->AliasFamilies()[aIndex].SetupFamilyCharMap(list); return; } if (aIndex >= list->NumFamilies()) { MOZ_ASSERT(false, "Family index out of range"); return; } list->Families()[aIndex].SetupFamilyCharMap(list); } bool gfxPlatformFontList::InitOtherFamilyNames(uint32_t aGeneration, bool aDefer) { auto list = SharedFontList(); MOZ_ASSERT(list); if (!list) { return false; } if (list->GetGeneration() != aGeneration) { return false; } if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { return false; } return InitOtherFamilyNames(aDefer); } uint32_t gfxPlatformFontList::GetGeneration() const { return SharedFontList() ? SharedFontList()->GetGeneration() : 0; } gfxPlatformFontList::FontPrefs::FontPrefs() { // This must be created on the main thread, so that we can safely use the // Preferences service. Once created, it can be read from any thread. MOZ_ASSERT(NS_IsMainThread()); Init(); } void gfxPlatformFontList::FontPrefs::Init() { if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { return; } nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch(); if (!prefRootBranch) { return; } nsTArray 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