/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- * 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 "gfxUserFontSet.h" #include "gfxPlatform.h" #include "gfxPrefs.h" #include "nsContentPolicyUtils.h" #include "nsUnicharUtils.h" #include "nsNetUtil.h" #include "nsIJARChannel.h" #include "nsIProtocolHandler.h" #include "nsIPrincipal.h" #include "nsIZipReader.h" #include "gfxFontConstants.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "mozilla/gfx/2D.h" #include "gfxPlatformFontList.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/PostTraversalTask.h" #include "opentype-sanitiser.h" #include "ots-memory-stream.h" using namespace mozilla; mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() { static LazyLogModule sLog("userfonts"); return sLog; } #define LOG(args) MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug) static uint64_t sFontSetGeneration = 0; // Based on ots::ExpandingMemoryStream from ots-memory-stream.h, // adapted to use Mozilla allocators and to allow the final // memory buffer to be adopted by the client. class ExpandingMemoryStream : public ots::OTSStream { public: ExpandingMemoryStream(size_t initial, size_t limit) : mLength(initial), mLimit(limit), mOff(0) { mPtr = moz_xmalloc(mLength); } ~ExpandingMemoryStream() { free(mPtr); } // Return the buffer, resized to fit its contents (as it may have been // over-allocated during growth), and give up ownership of it so the // caller becomes responsible to call free() when finished with it. void* forget() { void* p = moz_xrealloc(mPtr, mOff); mPtr = nullptr; return p; } bool WriteRaw(const void* data, size_t length) { if ((mOff + length > mLength) || (mLength > std::numeric_limits::max() - mOff)) { if (mLength == mLimit) { return false; } size_t newLength = (mLength + 1) * 2; if (newLength < mLength) { return false; } if (newLength > mLimit) { newLength = mLimit; } mPtr = moz_xrealloc(mPtr, newLength); mLength = newLength; return WriteRaw(data, length); } std::memcpy(static_cast(mPtr) + mOff, data, length); mOff += length; return true; } bool Seek(off_t position) { if (position < 0) { return false; } if (static_cast(position) > mLength) { return false; } mOff = position; return true; } off_t Tell() const { return mOff; } private: void* mPtr; size_t mLength; const size_t mLimit; off_t mOff; }; gfxUserFontEntry::gfxUserFontEntry(gfxUserFontSet* aFontSet, const nsTArray& aFontFaceSrcList, uint32_t aWeight, int32_t aStretch, uint8_t aStyle, const nsTArray& aFeatureSettings, uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, uint8_t aFontDisplay) : gfxFontEntry(NS_LITERAL_STRING("userfont")), mUserFontLoadState(STATUS_NOT_LOADED), mFontDataLoadingState(NOT_LOADING), mUnsupportedFormat(false), mFontDisplay(aFontDisplay), mLoader(nullptr), mFontSet(aFontSet) { MOZ_ASSERT(aWeight != 0, "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead"); mIsUserFontContainer = true; mSrcList = aFontFaceSrcList; mSrcIndex = 0; mWeight = aWeight; mStretch = aStretch; mStyle = aStyle; mFeatureSettings.AppendElements(aFeatureSettings); mLanguageOverride = aLanguageOverride; mCharacterMap = aUnicodeRanges; } gfxUserFontEntry::~gfxUserFontEntry() { // Assert that we don't drop any gfxUserFontEntry objects during a Servo // traversal, since PostTraversalTask objects can hold raw pointers to // gfxUserFontEntry objects. MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal()); } bool gfxUserFontEntry::Matches(const nsTArray& aFontFaceSrcList, uint32_t aWeight, int32_t aStretch, uint8_t aStyle, const nsTArray& aFeatureSettings, uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, uint8_t aFontDisplay) { return mWeight == aWeight && mStretch == aStretch && mStyle == aStyle && mFeatureSettings == aFeatureSettings && mLanguageOverride == aLanguageOverride && mSrcList == aFontFaceSrcList && mFontDisplay == aFontDisplay && ((!aUnicodeRanges && !mCharacterMap) || (aUnicodeRanges && mCharacterMap && mCharacterMap->Equals(aUnicodeRanges))); } gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, bool aNeedsBold) { NS_NOTREACHED("should only be creating a gfxFont" " with an actual platform font entry"); // userfont entry is a container, can't create font from the container return nullptr; } class gfxOTSContext : public ots::OTSContext { public: explicit gfxOTSContext(gfxUserFontEntry* aUserFontEntry) : mUserFontEntry(aUserFontEntry) { // Whether to apply OTS validation to OpenType Layout tables mCheckOTLTables = gfxPrefs::ValidateOTLTables(); // Whether to preserve Variation tables in downloaded fonts mKeepVariationTables = gfxPrefs::KeepVariationTables(); } virtual ots::TableAction GetTableAction(uint32_t aTag) override { // Preserve Graphite, color glyph and SVG tables, // and possibly OTL and Variation tables (depending on prefs) if ((!mCheckOTLTables && (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') || aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') || aTag == TRUETYPE_TAG('G', 'S', 'U', 'B'))) || (mKeepVariationTables && (aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') || aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') || aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') || aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') || aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') || aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') || aTag == TRUETYPE_TAG('V', 'V', 'A', 'R'))) || aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') || aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') || aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') || aTag == TRUETYPE_TAG('G', 'l', 'a', 't') || aTag == TRUETYPE_TAG('F', 'e', 'a', 't') || aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') || aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') || aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) { return ots::TABLE_ACTION_PASSTHRU; } return ots::TABLE_ACTION_DEFAULT; } virtual void Message(int level, const char* format, ...) MSGFUNC_FMT_ATTR override { va_list va; va_start(va, format); nsCString msg; msg.AppendPrintf(format, va); va_end(va); if (level > 0) { // For warnings (rather than errors that cause the font to fail), // we only report the first instance of any given message. if (mWarningsIssued.Contains(msg)) { return; } mWarningsIssued.PutEntry(msg); } mUserFontEntry->mFontSet->LogMessage(mUserFontEntry, msg.get()); } private: gfxUserFontEntry* mUserFontEntry; nsTHashtable mWarningsIssued; bool mCheckOTLTables; bool mKeepVariationTables; }; // Call the OTS library to sanitize an sfnt before attempting to use it. // Returns a newly-allocated block, or nullptr in case of fatal errors. const uint8_t* gfxUserFontEntry::SanitizeOpenTypeData(const uint8_t* aData, uint32_t aLength, uint32_t& aSaneLength, gfxUserFontType aFontType) { if (aFontType == GFX_USERFONT_UNKNOWN) { aSaneLength = 0; return nullptr; } uint32_t lengthHint = aLength; if (aFontType == GFX_USERFONT_WOFF) { lengthHint *= 2; } else if (aFontType == GFX_USERFONT_WOFF2) { lengthHint *= 3; } // limit output/expansion to 256MB ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256); gfxOTSContext otsContext(this); if (!otsContext.Process(&output, aData, aLength)) { // Failed to decode/sanitize the font, so discard it. aSaneLength = 0; return nullptr; } aSaneLength = output.Tell(); return static_cast(output.forget()); } void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, bool aPrivate, const nsAString& aOriginalName, FallibleTArray* aMetadata, uint32_t aMetaOrigLen, uint8_t aCompression) { if (!aFontEntry->mUserFontData) { aFontEntry->mUserFontData = MakeUnique(); } gfxUserFontData* userFontData = aFontEntry->mUserFontData.get(); userFontData->mSrcIndex = mSrcIndex; const gfxFontFaceSrc& src = mSrcList[mSrcIndex]; switch (src.mSourceType) { case gfxFontFaceSrc::eSourceType_Local: userFontData->mLocalName = src.mLocalName; break; case gfxFontFaceSrc::eSourceType_URL: userFontData->mURI = src.mURI; userFontData->mPrincipal = mPrincipal; break; case gfxFontFaceSrc::eSourceType_Buffer: userFontData->mIsBuffer = true; break; } userFontData->mPrivate = aPrivate; userFontData->mFormat = src.mFormatFlags; userFontData->mRealName = aOriginalName; if (aMetadata) { userFontData->mMetadata.SwapElements(*aMetadata); userFontData->mMetaOrigLen = aMetaOrigLen; userFontData->mCompression = aCompression; } } size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) + mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); // Not counting mURI and mPrincipal, as those will be shared. } /*virtual*/ gfxUserFontFamily::~gfxUserFontFamily() { // Should not be dropped by stylo MOZ_ASSERT(NS_IsMainThread()); } void gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName, nsACString& aURI) { aFamilyName.Assign(NS_ConvertUTF16toUTF8(mFamilyName)); aURI.Truncate(); if (mSrcIndex == mSrcList.Length()) { aURI.AppendLiteral("(end of source list)"); } else { if (mSrcList[mSrcIndex].mURI) { mSrcList[mSrcIndex].mURI->GetSpec(aURI); // If the source URI was very long, elide the middle of it. // In principle, the byte-oriented chopping here could leave us // with partial UTF-8 characters at the point where we cut it, // but it really doesn't matter as this is just for logging. const uint32_t kMaxURILengthForLogging = 256; // UTF-8 ellipsis, with spaces to allow additional wrap opportunities // in the resulting log message const char kEllipsis[] = { ' ', '\xE2', '\x80', '\xA6', ' ' }; if (aURI.Length() > kMaxURILengthForLogging) { aURI.Replace(kMaxURILengthForLogging / 2, aURI.Length() - kMaxURILengthForLogging, kEllipsis, ArrayLength(kEllipsis)); } } else { aURI.AppendLiteral("(invalid URI)"); } } } struct WOFFHeader { AutoSwap_PRUint32 signature; AutoSwap_PRUint32 flavor; AutoSwap_PRUint32 length; AutoSwap_PRUint16 numTables; AutoSwap_PRUint16 reserved; AutoSwap_PRUint32 totalSfntSize; AutoSwap_PRUint16 majorVersion; AutoSwap_PRUint16 minorVersion; AutoSwap_PRUint32 metaOffset; AutoSwap_PRUint32 metaCompLen; AutoSwap_PRUint32 metaOrigLen; AutoSwap_PRUint32 privOffset; AutoSwap_PRUint32 privLen; }; struct WOFF2Header { AutoSwap_PRUint32 signature; AutoSwap_PRUint32 flavor; AutoSwap_PRUint32 length; AutoSwap_PRUint16 numTables; AutoSwap_PRUint16 reserved; AutoSwap_PRUint32 totalSfntSize; AutoSwap_PRUint32 totalCompressedSize; AutoSwap_PRUint16 majorVersion; AutoSwap_PRUint16 minorVersion; AutoSwap_PRUint32 metaOffset; AutoSwap_PRUint32 metaCompLen; AutoSwap_PRUint32 metaOrigLen; AutoSwap_PRUint32 privOffset; AutoSwap_PRUint32 privLen; }; template void CopyWOFFMetadata(const uint8_t* aFontData, uint32_t aLength, FallibleTArray* aMetadata, uint32_t* aMetaOrigLen) { // This function may be called with arbitrary, unvalidated "font" data // from @font-face, so it needs to be careful to bounds-check, etc., // before trying to read anything. // This just saves a copy of the compressed data block; it does NOT check // that the block can be successfully decompressed, or that it contains // well-formed/valid XML metadata. if (aLength < sizeof(HeaderT)) { return; } const HeaderT* woff = reinterpret_cast(aFontData); uint32_t metaOffset = woff->metaOffset; uint32_t metaCompLen = woff->metaCompLen; if (!metaOffset || !metaCompLen || !woff->metaOrigLen) { return; } if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) { return; } if (!aMetadata->SetLength(woff->metaCompLen, fallible)) { return; } memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen); *aMetaOrigLen = woff->metaOrigLen; } void gfxUserFontEntry::LoadNextSrc() { NS_ASSERTION(mSrcIndex < mSrcList.Length(), "already at the end of the src list for user font"); NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || mUserFontLoadState == STATUS_LOAD_PENDING || mUserFontLoadState == STATUS_LOADING) && mFontDataLoadingState < LOADING_FAILED, "attempting to load a font that has either completed or failed"); if (mUserFontLoadState == STATUS_NOT_LOADED) { SetLoadState(STATUS_LOADING); mFontDataLoadingState = LOADING_STARTED; mUnsupportedFormat = false; } else { // we were already loading; move to the next source, // but don't reset state - if we've already timed out, // that counts against the new download mSrcIndex++; } DoLoadNextSrc(false); } void gfxUserFontEntry::ContinueLoad() { MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING); MOZ_ASSERT(mSrcList[mSrcIndex].mSourceType == gfxFontFaceSrc::eSourceType_URL); DoLoadNextSrc(true); } void gfxUserFontEntry::DoLoadNextSrc(bool aForceAsync) { uint32_t numSrc = mSrcList.Length(); // load each src entry in turn, until a local face is found // or a download begins successfully while (mSrcIndex < numSrc) { gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex]; // src local ==> lookup and load immediately if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) { // Don't look up local fonts if the font whitelist is being used. gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); gfxFontEntry* fe = pfl && pfl->IsFontFamilyWhitelistActive() ? nullptr : gfxPlatform::GetPlatform()->LookupLocalFont(currSrc.mLocalName, mWeight, mStretch, mStyle); nsTArray fontSets; GetUserFontSets(fontSets); for (gfxUserFontSet* fontSet : fontSets) { // We need to note on each gfxUserFontSet that contains the user // font entry that we used a local() rule. fontSet->SetLocalRulesUsed(); } if (fe) { LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n", mFontSet, mSrcIndex, NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(), NS_ConvertUTF16toUTF8(mFamilyName).get(), uint32_t(mFontSet->mGeneration))); fe->mFeatureSettings.AppendElements(mFeatureSettings); fe->mLanguageOverride = mLanguageOverride; fe->mFamilyName = mFamilyName; // For src:local(), we don't care whether the request is from // a private window as there's no issue of caching resources; // local fonts are just available all the time. StoreUserFontData(fe, false, nsString(), nullptr, 0, gfxUserFontData::kUnknownCompression); mPlatformFontEntry = fe; SetLoadState(STATUS_LOADED); Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, currSrc.mSourceType + 1); return; } else { LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", mFontSet, mSrcIndex, NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(), NS_ConvertUTF16toUTF8(mFamilyName).get())); } } // src url ==> start the load process else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) { if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI, currSrc.mFormatFlags)) { if (ServoStyleSet* set = ServoStyleSet::Current()) { set->AppendTask(PostTraversalTask::LoadFontEntry(this)); SetLoadState(STATUS_LOAD_PENDING); return; } nsIPrincipal* principal = nullptr; bool bypassCache; nsresult rv = mFontSet->CheckFontLoad(&currSrc, &principal, &bypassCache); if (NS_SUCCEEDED(rv) && principal != nullptr) { if (!bypassCache) { // see if we have an existing entry for this source gfxFontEntry* fe = gfxUserFontSet:: UserFontCache::GetFont(currSrc.mURI, principal, this, mFontSet->GetPrivateBrowsing()); if (fe) { mPlatformFontEntry = fe; SetLoadState(STATUS_LOADED); if (LOG_ENABLED()) { LOG(("userfonts (%p) [src %d] " "loaded uri from cache: (%s) for (%s)\n", mFontSet, mSrcIndex, currSrc.mURI->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(mFamilyName).get())); } return; } } // record the principal returned by CheckFontLoad, // for use when creating a channel // and when caching the loaded entry mPrincipal = principal; bool loadDoesntSpin = false; if (!aForceAsync) { rv = NS_URIChainHasFlags(currSrc.mURI, nsIProtocolHandler::URI_SYNC_LOAD_IS_OK, &loadDoesntSpin); } if (NS_SUCCEEDED(rv) && loadDoesntSpin) { uint8_t* buffer = nullptr; uint32_t bufferLength = 0; // sync load font immediately rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer, bufferLength); if (NS_SUCCEEDED(rv) && LoadPlatformFont(buffer, bufferLength)) { SetLoadState(STATUS_LOADED); Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, currSrc.mSourceType + 1); return; } else { mFontSet->LogMessage(this, "font load failed", nsIScriptError::errorFlag, rv); } } else { // otherwise load font async rv = mFontSet->StartLoad(this, &currSrc); bool loadOK = NS_SUCCEEDED(rv); if (loadOK) { if (LOG_ENABLED()) { LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", mFontSet, mSrcIndex, currSrc.mURI->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(mFamilyName).get())); } return; } else { mFontSet->LogMessage(this, "download failed", nsIScriptError::errorFlag, rv); } } } else { mFontSet->LogMessage(this, "download not allowed", nsIScriptError::errorFlag, rv); } } else { // We don't log a warning to the web console yet, // as another source may load successfully mUnsupportedFormat = true; } } // FontFace buffer ==> load immediately else { MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer); uint8_t* buffer = nullptr; uint32_t bufferLength = 0; // sync load font immediately currSrc.mBuffer->TakeBuffer(buffer, bufferLength); if (buffer && LoadPlatformFont(buffer, bufferLength)) { // LoadPlatformFont takes ownership of the buffer, so no need // to free it here. SetLoadState(STATUS_LOADED); Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, currSrc.mSourceType + 1); return; } else { mFontSet->LogMessage(this, "font load failed", nsIScriptError::errorFlag); } } mSrcIndex++; } if (mUnsupportedFormat) { mFontSet->LogMessage(this, "no supported format found", nsIScriptError::warningFlag); } // all src's failed; mark this entry as unusable (so fallback will occur) LOG(("userfonts (%p) failed all src for (%s)\n", mFontSet, NS_ConvertUTF16toUTF8(mFamilyName).get())); mFontDataLoadingState = LOADING_FAILED; SetLoadState(STATUS_FAILED); } void gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState) { mUserFontLoadState = aLoadState; } MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc) bool gfxUserFontEntry::LoadPlatformFont(const uint8_t* aFontData, uint32_t& aLength) { NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || mUserFontLoadState == STATUS_LOAD_PENDING || mUserFontLoadState == STATUS_LOADING) && mFontDataLoadingState < LOADING_FAILED, "attempting to load a font that has either completed or failed"); gfxFontEntry* fe = nullptr; gfxUserFontType fontType = gfxFontUtils::DetermineFontDataType(aFontData, aLength); Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(fontType)); // Unwrap/decompress/sanitize or otherwise munge the downloaded data // to make a usable sfnt structure. // Because platform font activation code may replace the name table // in the font with a synthetic one, we save the original name so that // it can be reported via the nsIDOMFontFace API. nsAutoString originalFullName; // Call the OTS sanitizer; this will also decode WOFF to sfnt // if necessary. The original data in aFontData is left unchanged. uint32_t saneLen; uint32_t fontCompressionRatio = 0; size_t computedSize = 0; const uint8_t* saneData = SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType); if (!saneData) { mFontSet->LogMessage(this, "rejected by sanitizer"); } else { // Check whether saneData is a known OpenType format; it might be // a TrueType Collection, which OTS would accept but we don't yet // know how to handle. If so, discard. if (gfxFontUtils::DetermineFontDataType(saneData, saneLen) != GFX_USERFONT_OPENTYPE) { mFontSet->LogMessage(this, "not a supported OpenType format"); free((void*)saneData); saneData = nullptr; } } if (saneData) { if (saneLen) { fontCompressionRatio = uint32_t(100.0 * aLength / saneLen + 0.5); if (fontType == GFX_USERFONT_WOFF || fontType == GFX_USERFONT_WOFF2) { Telemetry::Accumulate(fontType == GFX_USERFONT_WOFF ? Telemetry::WEBFONT_COMPRESSION_WOFF : Telemetry::WEBFONT_COMPRESSION_WOFF2, fontCompressionRatio); } } // The sanitizer ensures that we have a valid sfnt and a usable // name table, so this should never fail unless we're out of // memory, and GetFullNameFromSFNT is not directly exposed to // arbitrary/malicious data from the web. gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen, originalFullName); // Record size for memory reporting purposes. We measure this now // because by the time we potentially want to collect reports, this // data block may have been handed off to opaque OS font APIs that // don't allow us to retrieve or measure it directly. // The *OnAlloc function will also tell DMD about this block, as the // OS font code may hold on to it for an extended period. computedSize = UserFontMallocSizeOfOnAlloc(saneData); // Here ownership of saneData is passed to the platform, // which will delete it when no longer required fe = gfxPlatform::GetPlatform()->MakePlatformFont(mName, mWeight, mStretch, mStyle, saneData, saneLen); if (!fe) { mFontSet->LogMessage(this, "not usable by platform"); } } if (fe) { fe->mComputedSizeOfUserFont = computedSize; // Save a copy of the metadata block (if present) for nsIDOMFontFace // to use if required. Ownership of the metadata block will be passed // to the gfxUserFontData record below. FallibleTArray metadata; uint32_t metaOrigLen = 0; uint8_t compression = gfxUserFontData::kUnknownCompression; if (fontType == GFX_USERFONT_WOFF) { CopyWOFFMetadata(aFontData, aLength, &metadata, &metaOrigLen); compression = gfxUserFontData::kZlibCompression; } else if (fontType == GFX_USERFONT_WOFF2) { CopyWOFFMetadata(aFontData, aLength, &metadata, &metaOrigLen); compression = gfxUserFontData::kBrotliCompression; } // copy OpenType feature/language settings from the userfont entry to the // newly-created font entry fe->mFeatureSettings.AppendElements(mFeatureSettings); fe->mLanguageOverride = mLanguageOverride; fe->mFamilyName = mFamilyName; StoreUserFontData(fe, mFontSet->GetPrivateBrowsing(), originalFullName, &metadata, metaOrigLen, compression); if (LOG_ENABLED()) { LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) " "(%p) gen: %8.8x compress: %d%%\n", mFontSet, mSrcIndex, mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(mFamilyName).get(), this, uint32_t(mFontSet->mGeneration), fontCompressionRatio)); } mPlatformFontEntry = fe; SetLoadState(STATUS_LOADED); gfxUserFontSet::UserFontCache::CacheFont(fe); } else { if (LOG_ENABLED()) { LOG(("userfonts (%p) [src %d] failed uri: (%s) for (%s)" " error making platform font\n", mFontSet, mSrcIndex, mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(mFamilyName).get())); } } // The downloaded data can now be discarded; the font entry is using the // sanitized copy free((void*)aFontData); return fe != nullptr; } void gfxUserFontEntry::Load() { if (mUserFontLoadState == STATUS_NOT_LOADED) { LoadNextSrc(); } } void gfxUserFontEntry::IncrementGeneration() { nsTArray fontSets; GetUserFontSets(fontSets); for (gfxUserFontSet* fontSet : fontSets) { fontSet->IncrementGeneration(); } } // This is called when a font download finishes. // Ownership of aFontData passes in here, and the font set must // ensure that it is eventually deleted via free(). bool gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData, uint32_t aLength, nsresult aDownloadStatus) { // forget about the loader, as we no longer potentially need to cancel it // if the entry is obsoleted mLoader = nullptr; // download successful, make platform font using font data if (NS_SUCCEEDED(aDownloadStatus) && mFontDataLoadingState != LOADING_TIMED_OUT) { bool loaded = LoadPlatformFont(aFontData, aLength); aFontData = nullptr; if (loaded) { IncrementGeneration(); return true; } } else { // download failed mFontSet->LogMessage(this, (mFontDataLoadingState != LOADING_TIMED_OUT ? "download failed" : "download timed out"), nsIScriptError::errorFlag, aDownloadStatus); } if (aFontData) { free((void*)aFontData); } // error occurred, load next src if load not yet timed out if (mFontDataLoadingState != LOADING_TIMED_OUT) { LoadNextSrc(); } // We ignore the status returned by LoadNext(); // even if loading failed, we need to bump the font-set generation // and return true in order to trigger reflow, so that fallback // will be used where the text was "masked" by the pending download IncrementGeneration(); return true; } void gfxUserFontEntry::GetUserFontSets(nsTArray& aResult) { aResult.Clear(); aResult.AppendElement(mFontSet); } gfxUserFontSet::gfxUserFontSet() : mFontFamilies(4), mLocalRulesUsed(false), mRebuildLocalRules(false), mDownloadCount(0), mDownloadSize(0) { IncrementGeneration(true); gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); if (fp) { fp->AddUserFontSet(this); } } gfxUserFontSet::~gfxUserFontSet() { gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); if (fp) { fp->RemoveUserFontSet(this); } } already_AddRefed gfxUserFontSet::FindOrCreateUserFontEntry( const nsAString& aFamilyName, const nsTArray& aFontFaceSrcList, uint32_t aWeight, int32_t aStretch, uint8_t aStyle, const nsTArray& aFeatureSettings, uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, uint8_t aFontDisplay) { RefPtr entry; // If there's already a userfont entry in the family whose descriptors all match, // we can just move it to the end of the list instead of adding a new // face that will always "shadow" the old one. // Note that we can't do this for platform font entries, even if the // style descriptors match, as they might have had a different source list, // but we no longer have the old source list available to check. gfxUserFontFamily* family = LookupFamily(aFamilyName); if (family) { entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay); } if (!entry) { entry = CreateUserFontEntry(aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay); entry->mFamilyName = aFamilyName; } return entry.forget(); } already_AddRefed gfxUserFontSet::CreateUserFontEntry( const nsTArray& aFontFaceSrcList, uint32_t aWeight, int32_t aStretch, uint8_t aStyle, const nsTArray& aFeatureSettings, uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, uint8_t aFontDisplay) { RefPtr userFontEntry = new gfxUserFontEntry(this, aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay); return userFontEntry.forget(); } gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry( gfxUserFontFamily* aFamily, const nsTArray& aFontFaceSrcList, uint32_t aWeight, int32_t aStretch, uint8_t aStyle, const nsTArray& aFeatureSettings, uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, uint8_t aFontDisplay) { MOZ_ASSERT(aWeight != 0, "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead"); nsTArray>& fontList = aFamily->GetFontList(); for (size_t i = 0, count = fontList.Length(); i < count; i++) { if (!fontList[i]->mIsUserFontContainer) { continue; } gfxUserFontEntry* existingUserFontEntry = static_cast(fontList[i].get()); if (!existingUserFontEntry->Matches(aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay)) { continue; } return existingUserFontEntry; } return nullptr; } void gfxUserFontSet::AddUserFontEntry(const nsAString& aFamilyName, gfxUserFontEntry* aUserFontEntry) { gfxUserFontFamily* family = GetFamily(aFamilyName); family->AddFontEntry(aUserFontEntry); if (LOG_ENABLED()) { LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %d " "stretch: %d display: %d", this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aUserFontEntry, (aUserFontEntry->IsItalic() ? "italic" : (aUserFontEntry->IsOblique() ? "oblique" : "normal")), aUserFontEntry->Weight(), aUserFontEntry->Stretch(), aUserFontEntry->GetFontDisplay())); } } void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) { // add one, increment again if zero ++sFontSetGeneration; if (sFontSetGeneration == 0) ++sFontSetGeneration; mGeneration = sFontSetGeneration; if (aIsRebuild) { mRebuildGeneration = mGeneration; } } void gfxUserFontSet::RebuildLocalRules() { if (mLocalRulesUsed) { mRebuildLocalRules = true; DoRebuildUserFontSet(); } } gfxUserFontFamily* gfxUserFontSet::LookupFamily(const nsAString& aFamilyName) const { nsAutoString key(aFamilyName); ToLowerCase(key); return mFontFamilies.GetWeak(key); } bool gfxUserFontSet::ContainsUserFontSetFonts(const FontFamilyList& aFontList) const { for (const FontFamilyName& name : aFontList.GetFontlist()) { if (name.mType != eFamily_named && name.mType != eFamily_named_quoted) { continue; } if (LookupFamily(name.mName)) { return true; } } return false; } gfxUserFontFamily* gfxUserFontSet::GetFamily(const nsAString& aFamilyName) { nsAutoString key(aFamilyName); ToLowerCase(key); gfxUserFontFamily* family = mFontFamilies.GetWeak(key); if (!family) { family = new gfxUserFontFamily(aFamilyName); mFontFamilies.Put(key, family); } return family; } /////////////////////////////////////////////////////////////////////////////// // gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts // across pages/fontsets rather than instantiating new platform fonts. // // Entries are added to this cache when a platform font is instantiated from // downloaded data, and removed when the platform font entry is destroyed. // We don't need to use a timed expiration scheme here because the gfxFontEntry // for a downloaded font will be kept alive by its corresponding gfxFont // instance(s) until they are deleted, and *that* happens using an expiration // tracker (gfxFontCache). The result is that the downloaded font instances // recorded here will persist between pages and can get reused (provided the // source URI and principal match, of course). /////////////////////////////////////////////////////////////////////////////// nsTHashtable* gfxUserFontSet::UserFontCache::sUserFonts = nullptr; NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver) NS_IMETHODIMP gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!sUserFonts) { return NS_OK; } if (!strcmp(aTopic, "cacheservice:empty-cache")) { for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { if (!i.Get()->IsPersistent()) { i.Remove(); } } } else if (!strcmp(aTopic, "last-pb-context-exited")) { for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { if (i.Get()->IsPrivate()) { i.Remove(); } } } else if (!strcmp(aTopic, "xpcom-shutdown")) { for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { i.Get()->GetFontEntry()->DisconnectSVG(); } } else { NS_NOTREACHED("unexpected topic"); } return NS_OK; } static bool IgnorePrincipal(nsIURI* aURI) { nsresult rv; bool inherits = false; rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &inherits); return NS_SUCCEEDED(rv) && inherits; } bool gfxUserFontSet::UserFontCache::Entry::KeyEquals(const KeyTypePointer aKey) const { const gfxFontEntry* fe = aKey->mFontEntry; // CRC32 checking mode if (mLength || aKey->mLength) { if (aKey->mLength != mLength || aKey->mCRC32 != mCRC32) { return false; } } else { bool result; if (NS_FAILED(mURI->Equals(aKey->mURI, &result)) || !result) { return false; } // For data: URIs, we don't care about the principal; otherwise, check it. if (!IgnorePrincipal(mURI)) { NS_ASSERTION(mPrincipal && aKey->mPrincipal, "only data: URIs are allowed to omit the principal"); if (NS_FAILED(mPrincipal->Equals(aKey->mPrincipal, &result)) || !result) { return false; } } if (mPrivate != aKey->mPrivate) { return false; } } if (mFontEntry->mStyle != fe->mStyle || mFontEntry->mWeight != fe->mWeight || mFontEntry->mStretch != fe->mStretch || mFontEntry->mFeatureSettings != fe->mFeatureSettings || mFontEntry->mLanguageOverride != fe->mLanguageOverride || mFontEntry->mFamilyName != fe->mFamilyName) { return false; } return true; } void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry, EntryPersistence aPersistence) { NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0, "caching a font associated with no family yet"); // if caching is disabled, simply return if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { return; } gfxUserFontData* data = aFontEntry->mUserFontData.get(); if (data->mIsBuffer) { #ifdef DEBUG_USERFONT_CACHE printf("userfontcache skipped fontentry with buffer source: %p\n", aFontEntry); #endif return; } if (!sUserFonts) { sUserFonts = new nsTHashtable; nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { Flusher* flusher = new Flusher; obs->AddObserver(flusher, "cacheservice:empty-cache", false); obs->AddObserver(flusher, "last-pb-context-exited", false); obs->AddObserver(flusher, "xpcom-shutdown", false); } // Create and register a memory reporter for sUserFonts. // This reporter is never unregistered, but that's OK because // the reporter checks whether sUserFonts is null, so it would // be safe to call even after UserFontCache::Shutdown has deleted // the cache. RegisterStrongMemoryReporter(new MemoryReporter()); } if (data->mLength) { MOZ_ASSERT(aPersistence == kPersistent); MOZ_ASSERT(!data->mPrivate); sUserFonts->PutEntry(Key(data->mCRC32, data->mLength, aFontEntry, data->mPrivate, aPersistence)); } else { MOZ_ASSERT(aPersistence == kDiscardable); // For data: URIs, the principal is ignored; anyone who has the same // data: URI is able to load it and get an equivalent font. // Otherwise, the principal is used as part of the cache key. nsIPrincipal* principal; if (IgnorePrincipal(data->mURI)) { principal = nullptr; } else { principal = data->mPrincipal; } sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate, aPersistence)); } #ifdef DEBUG_USERFONT_CACHE printf("userfontcache added fontentry: %p\n", aFontEntry); Dump(); #endif } void gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry* aFontEntry) { if (!sUserFonts) { // if we've already deleted the cache (i.e. during shutdown), // just ignore this return; } // We can't simply use RemoveEntry here because it's possible the principal // may have changed since the font was cached, in which case the lookup // would no longer find the entry (bug 838105). for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { if (i.Get()->GetFontEntry() == aFontEntry) { i.Remove(); } } #ifdef DEBUG_USERFONT_CACHE printf("userfontcache removed fontentry: %p\n", aFontEntry); Dump(); #endif } gfxFontEntry* gfxUserFontSet::UserFontCache::GetFont(nsIURI* aSrcURI, nsIPrincipal* aPrincipal, gfxUserFontEntry* aUserFontEntry, bool aPrivate) { if (!sUserFonts || Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { return nullptr; } // We have to perform another content policy check here to prevent // cache poisoning. E.g. a.com loads a font into the cache but // b.com has a CSP not allowing any fonts to be loaded. if (!aUserFontEntry->mFontSet->IsFontLoadAllowed(aSrcURI, aPrincipal)) { return nullptr; } // Ignore principal when looking up a data: URI. nsIPrincipal* principal; if (IgnorePrincipal(aSrcURI)) { principal = nullptr; } else { principal = aPrincipal; } Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, principal, aUserFontEntry, aPrivate)); if (entry) { return entry->GetFontEntry(); } // The channel is never openend; to be conservative we use the most // restrictive security flag: SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS. nsCOMPtr chan; if (NS_FAILED(NS_NewChannel(getter_AddRefs(chan), aSrcURI, aPrincipal, nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, nsIContentPolicy::TYPE_FONT))) { return nullptr; } nsCOMPtr jarchan = do_QueryInterface(chan); if (!jarchan) { return nullptr; } nsCOMPtr zipentry; if (NS_FAILED(jarchan->GetZipEntry(getter_AddRefs(zipentry)))) { return nullptr; } uint32_t crc32, length; zipentry->GetCRC32(&crc32); zipentry->GetRealSize(&length); entry = sUserFonts->GetEntry(Key(crc32, length, aUserFontEntry, aPrivate)); if (entry) { return entry->GetFontEntry(); } return nullptr; } void gfxUserFontSet::UserFontCache::Shutdown() { if (sUserFonts) { delete sUserFonts; sUserFonts = nullptr; } } MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf) void gfxUserFontSet::UserFontCache::Entry::ReportMemory( nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) { MOZ_ASSERT(mFontEntry); nsAutoCString path("explicit/gfx/user-fonts/font("); if (aAnonymize) { path.AppendPrintf("", this); } else { NS_ConvertUTF16toUTF8 familyName(mFontEntry->mFamilyName); path.AppendPrintf("family=%s", familyName.get()); if (mURI) { nsCString spec = mURI->GetSpecOrDefault(); spec.ReplaceChar('/', '\\'); // Some fonts are loaded using horrendously-long data: URIs; // truncate those before reporting them. bool isData; if (NS_SUCCEEDED(mURI->SchemeIs("data", &isData)) && isData && spec.Length() > 255) { spec.Truncate(252); spec.Append("..."); } path.AppendPrintf(", url=%s", spec.get()); } if (mPrincipal) { nsCOMPtr uri; mPrincipal->GetURI(getter_AddRefs(uri)); if (uri) { nsCString spec = uri->GetSpecOrDefault(); if (!spec.IsEmpty()) { // Include a clue as to who loaded this resource. (Note // that because of font entry sharing, other pages may now // be using this resource, and the original page may not // even be loaded any longer.) spec.ReplaceChar('/', '\\'); path.AppendPrintf(", principal=%s", spec.get()); } } } } path.Append(')'); aHandleReport->Callback( EmptyCString(), path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf), NS_LITERAL_CSTRING("Memory used by @font-face resource."), aData); } NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter, nsIMemoryReporter) NS_IMETHODIMP gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports( nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) { if (!sUserFonts) { return NS_OK; } for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) { it.Get()->ReportMemory(aHandleReport, aData, aAnonymize); } MOZ_COLLECT_REPORT( "explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES, sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf), "Memory used by the @font-face cache, not counting the actual font " "resources."); return NS_OK; } #ifdef DEBUG_USERFONT_CACHE void gfxUserFontSet::UserFontCache::Entry::Dump() { nsresult rv; nsAutoCString principalURISpec("(null)"); bool setDomain = false; if (mPrincipal) { nsCOMPtr principalURI; rv = mPrincipal->GetURI(getter_AddRefs(principalURI)); if (NS_SUCCEEDED(rv)) { principalURI->GetSpec(principalURISpec); } nsCOMPtr domainURI; mPrincipal->GetDomain(getter_AddRefs(domainURI)); if (domainURI) { setDomain = true; } } NS_ASSERTION(mURI, "null URI in userfont cache entry"); printf("userfontcache fontEntry: %p fonturihash: %8.8x " "family: %s domainset: %s principal: [%s]\n", mFontEntry, nsURIHashKey::HashKey(mURI), NS_ConvertUTF16toUTF8(mFontEntry->FamilyName()).get(), setDomain ? "true" : "false", principalURISpec.get()); } void gfxUserFontSet::UserFontCache::Dump() { if (!sUserFonts) { return; } printf("userfontcache dump count: %d ========\n", sUserFonts->Count()); for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) { it.Get()->Dump(); } printf("userfontcache dump ==================\n"); } #endif #undef LOG #undef LOG_ENABLED