diff options
Diffstat (limited to 'gfx/thebes/gfxUserFontSet.cpp')
-rw-r--r-- | gfx/thebes/gfxUserFontSet.cpp | 1439 |
1 files changed, 1439 insertions, 0 deletions
diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp new file mode 100644 index 000000000..23c26d9fe --- /dev/null +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -0,0 +1,1439 @@ +/* -*- 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 "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 "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<size_t>::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<char*>(mPtr) + mOff, data, length); + mOff += length; + return true; + } + + bool Seek(off_t position) { + if (position < 0) { + return false; + } + if (static_cast<size_t>(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<gfxFontFaceSrc>& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint8_t aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet* 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; + + if (aUnicodeRanges) { + mCharacterMap = new gfxCharacterMap(*aUnicodeRanges); + } +} + +gfxUserFontEntry::~gfxUserFontEntry() +{ +} + +bool +gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint8_t aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet* 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) {} + + virtual ots::TableAction GetTableAction(uint32_t aTag) override { + // Preserve Graphite, color glyph and SVG tables + if ( +#ifdef RELEASE_OR_BETA // For Beta/Release, also allow OT Layout tables through + // unchecked, and rely on harfbuzz to handle them safely. + aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') || + aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') || + aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') || +#endif + 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<nsCStringHashKey> mWarningsIssued; +}; + +// 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<const uint8_t*>(output.forget()); +} + +void +gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, + bool aPrivate, + const nsAString& aOriginalName, + FallibleTArray<uint8_t>* aMetadata, + uint32_t aMetaOrigLen, + uint8_t aCompression) +{ + if (!aFontEntry->mUserFontData) { + aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>(); + } + 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. +} + +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); + } 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<typename HeaderT> +void +CopyWOFFMetadata(const uint8_t* aFontData, + uint32_t aLength, + FallibleTArray<uint8_t>* 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<const HeaderT*>(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() +{ + uint32_t numSrc = mSrcList.Length(); + + NS_ASSERTION(mSrcIndex < numSrc, + "already at the end of the src list for user font"); + NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || + 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++; + } + + // 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<gfxUserFontSet*> 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)) { + + 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; + 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_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<uint8_t> metadata; + uint32_t metaOrigLen = 0; + uint8_t compression = gfxUserFontData::kUnknownCompression; + if (fontType == GFX_USERFONT_WOFF) { + CopyWOFFMetadata<WOFFHeader>(aFontData, aLength, + &metadata, &metaOrigLen); + compression = gfxUserFontData::kZlibCompression; + } else if (fontType == GFX_USERFONT_WOFF2) { + CopyWOFFMetadata<WOFF2Header>(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<gfxUserFontSet*> 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<gfxUserFontSet*>& 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<gfxUserFontEntry> +gfxUserFontSet::FindOrCreateUserFontEntry( + const nsAString& aFamilyName, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint8_t aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet* aUnicodeRanges, + uint8_t aFontDisplay) +{ + RefPtr<gfxUserFontEntry> 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<gfxUserFontEntry> +gfxUserFontSet::CreateUserFontEntry( + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint8_t aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet* aUnicodeRanges, + uint8_t aFontDisplay) +{ + + RefPtr<gfxUserFontEntry> userFontEntry = + new gfxUserFontEntry(this, aFontFaceSrcList, aWeight, + aStretch, aStyle, aFeatureSettings, + aLanguageOverride, aUnicodeRanges, aFontDisplay); + return userFontEntry.forget(); +} + +gfxUserFontEntry* +gfxUserFontSet::FindExistingUserFontEntry( + gfxUserFontFamily* aFamily, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint8_t aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet* aUnicodeRanges, + uint8_t aFontDisplay) +{ + MOZ_ASSERT(aWeight != 0, + "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead"); + + nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList(); + + for (size_t i = 0, count = fontList.Length(); i < count; i++) { + if (!fontList[i]->mIsUserFontContainer) { + continue; + } + + gfxUserFontEntry* existingUserFontEntry = + static_cast<gfxUserFontEntry*>(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())); + } +} + +gfxUserFontEntry* +gfxUserFontSet::FindUserFontEntryAndLoad(gfxFontFamily* aFamily, + const gfxFontStyle& aFontStyle, + bool& aNeedsBold, + bool& aWaitForUserFont) +{ + aWaitForUserFont = false; + gfxFontEntry* fe = aFamily->FindFontForStyle(aFontStyle, aNeedsBold); + NS_ASSERTION(!fe || fe->mIsUserFontContainer, + "should only have userfont entries in userfont families"); + if (!fe) { + return nullptr; + } + + gfxUserFontEntry* userFontEntry = static_cast<gfxUserFontEntry*>(fe); + + // start the load if it hasn't been loaded + userFontEntry->Load(); + if (userFontEntry->GetPlatformFontEntry()) { + return userFontEntry; + } + + aWaitForUserFont = userFontEntry->WaitForUserFont(); + return nullptr; +} + +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::Entry>* + 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<Entry>; + + nsCOMPtr<nsIObserverService> 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<nsIChannel> chan; + if (NS_FAILED(NS_NewChannel(getter_AddRefs(chan), + aSrcURI, + aPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, + nsIContentPolicy::TYPE_FONT))) { + return nullptr; + } + + nsCOMPtr<nsIJARChannel> jarchan = do_QueryInterface(chan); + if (!jarchan) { + return nullptr; + } + + nsCOMPtr<nsIZipEntry> 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("<anonymized-%p>", 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<nsIURI> 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<nsIURI> principalURI; + rv = mPrincipal->GetURI(getter_AddRefs(principalURI)); + if (NS_SUCCEEDED(rv)) { + principalURI->GetSpec(principalURISpec); + } + + nsCOMPtr<nsIURI> 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 |