diff options
Diffstat (limited to 'gfx/thebes/gfxUserFontSet.cpp')
-rw-r--r-- | gfx/thebes/gfxUserFontSet.cpp | 1428 |
1 files changed, 1428 insertions, 0 deletions
diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp new file mode 100644 index 0000000000..aeaf296200 --- /dev/null +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -0,0 +1,1428 @@ +/* -*- 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 "gfxUserFontSet.h" +#include "gfxPlatform.h" +#include "gfxFontConstants.h" +#include "mozilla/Atomics.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatformFontList.h" +#include "mozilla/PostTraversalTask.h" +#include "mozilla/dom/WorkerCommon.h" +#include "gfxOTSUtils.h" +#include "nsIFontLoadCompleteCallback.h" +#include "nsProxyRelease.h" +#include "nsContentUtils.h" +#include "nsTHashSet.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 Atomic<uint64_t> sFontSetGeneration(0); + +gfxUserFontEntry::gfxUserFontEntry(nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr) + : gfxFontEntry("userfont"_ns), + mUserFontLoadState(STATUS_NOT_LOADED), + mFontDataLoadingState(NOT_LOADING), + mSeenLocalSource(false), + mUnsupportedFormat(false), + mFontDisplay(aAttr.mFontDisplay), + mLoader(nullptr) { + mIsUserFontContainer = true; + mSrcList = std::move(aFontFaceSrcList); + mCurrentSrcIndex = 0; + mWeightRange = aAttr.mWeight; + mStretchRange = aAttr.mStretch; + mStyleRange = aAttr.mStyle; + mFeatureSettings = std::move(aAttr.mFeatureSettings); + mVariationSettings = std::move(aAttr.mVariationSettings); + mLanguageOverride = aAttr.mLanguageOverride; + SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges)); + mRangeFlags = aAttr.mRangeFlags; + mAscentOverride = aAttr.mAscentOverride; + mDescentOverride = aAttr.mDescentOverride; + mLineGapOverride = aAttr.mLineGapOverride; + mSizeAdjust = aAttr.mSizeAdjust; + mFamilyName = aAttr.mFamilyName; +} + +void gfxUserFontEntry::UpdateAttributes(gfxUserFontAttributes&& aAttr) { + MOZ_ASSERT(NS_IsMainThread()); + + // Remove the entry from the user font cache, if present there, as the cache + // key may no longer be correct with the new attributes. + gfxUserFontSet::UserFontCache::ForgetFont(this); + + mFontDisplay = aAttr.mFontDisplay; + mWeightRange = aAttr.mWeight; + mStretchRange = aAttr.mStretch; + mStyleRange = aAttr.mStyle; + mFeatureSettings = std::move(aAttr.mFeatureSettings); + mVariationSettings = std::move(aAttr.mVariationSettings); + mLanguageOverride = aAttr.mLanguageOverride; + SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges)); + mRangeFlags = aAttr.mRangeFlags; + mAscentOverride = aAttr.mAscentOverride; + mDescentOverride = aAttr.mDescentOverride; + mLineGapOverride = aAttr.mLineGapOverride; + mSizeAdjust = aAttr.mSizeAdjust; +} + +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(!gfxFontUtils::IsInServoTraversal()); +} + +bool gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + const gfxUserFontAttributes& aAttr) { + return mWeightRange == aAttr.mWeight && mStretchRange == aAttr.mStretch && + mStyleRange == aAttr.mStyle && + mFeatureSettings == aAttr.mFeatureSettings && + mVariationSettings == aAttr.mVariationSettings && + mLanguageOverride == aAttr.mLanguageOverride && + mSrcList == aFontFaceSrcList && mFontDisplay == aAttr.mFontDisplay && + mRangeFlags == aAttr.mRangeFlags && + mAscentOverride == aAttr.mAscentOverride && + mDescentOverride == aAttr.mDescentOverride && + mLineGapOverride == aAttr.mLineGapOverride && + mSizeAdjust == aAttr.mSizeAdjust && + ((!aAttr.mUnicodeRanges && !mCharacterMap) || + (aAttr.mUnicodeRanges && mCharacterMap && + GetCharacterMap()->Equals(aAttr.mUnicodeRanges))); +} + +gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + MOZ_ASSERT_UNREACHABLE( + "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 MOZ_STACK_CLASS gfxOTSMessageContext : public gfxOTSContext { + public: + virtual ~gfxOTSMessageContext() { + MOZ_ASSERT(mMessages.IsEmpty(), "should have called TakeMessages"); + } + + virtual void Message(int level, const char* format, + ...) MSGFUNC_FMT_ATTR override { + va_list va; + + // Special-case glyph bounding box warnings: collect all bad glyph IDs, + // so we can issue a single message at the end. + if (level > 0 && strstr(format, "bbox was incorrect")) { + // Extract the glyph ID from the message: it follows the last space in + // the message string. + const char* lastSpace = strrchr(format, ' '); + if (lastSpace) { + int gid = atoi(lastSpace + 1); + mBadBBoxGlyphs.AppendElement(gid); + } + return; + } + + va_start(va, format); + + nsCString msg; + msg.AppendVprintf(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.EnsureInserted(msg)) { + return; + } + } + + mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, level}); + } + + bool Process(ots::OTSStream* aOutput, const uint8_t* aInput, size_t aLength, + nsTArray<gfxUserFontEntry::OTSMessage>& aMessages) { + bool ok = ots::OTSContext::Process(aOutput, aInput, aLength); + aMessages = TakeMessages(); + return ok; + } + + nsTArray<gfxUserFontEntry::OTSMessage>&& TakeMessages() { + if (!mBadBBoxGlyphs.IsEmpty()) { + nsAutoCString msg("Glyph bbox was incorrect (glyph ids"); + for (const auto gid : mBadBBoxGlyphs) { + msg.Append(" "); + msg.AppendInt(gid); + } + msg.Append(")"); + mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, 1}); + mBadBBoxGlyphs.Clear(); + } + return std::move(mMessages); + } + + private: + nsTHashSet<nsCString> mWarningsIssued; + nsTArray<gfxUserFontEntry::OTSMessage> mMessages; + nsTArray<uint16_t> mBadBBoxGlyphs; +}; + +// 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& aSanitaryLength, + gfxUserFontType& aFontType, nsTArray<OTSMessage>& aMessages) { + aFontType = gfxFontUtils::DetermineFontDataType(aData, aLength); + Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(aFontType)); + + size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(aLength, aFontType); + if (!lengthHint) { + aSanitaryLength = 0; + return nullptr; + } + + gfxOTSExpandingMemoryStream<gfxOTSMozAlloc> output(lengthHint); + + gfxOTSMessageContext otsContext; + if (!otsContext.Process(&output, aData, aLength, aMessages)) { + // Failed to decode/sanitize the font, so discard it. + aSanitaryLength = 0; + return nullptr; + } + + aSanitaryLength = output.Tell(); + return static_cast<const uint8_t*>(output.forget()); +} + +void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, + uint32_t aSrcIndex, bool aPrivate, + const nsACString& 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 = aSrcIndex; + const gfxFontFaceSrc& src = mSrcList[aSrcIndex]; + 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->mTechFlags = src.mTechFlags; + userFontData->mFormatHint = src.mFormatHint; + userFontData->mRealName = aOriginalName; + if (aMetadata) { + userFontData->mMetadata = std::move(*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(!gfxFontUtils::IsInServoTraversal()); +} + +already_AddRefed<gfxFontSrcPrincipal> gfxFontFaceSrc::LoadPrincipal( + const gfxUserFontSet& aFontSet) const { + MOZ_ASSERT(mSourceType == eSourceType_URL); + if (mUseOriginPrincipal) { + MOZ_ASSERT(mOriginPrincipal); + return RefPtr{mOriginPrincipal}.forget(); + } + return aFontSet.GetStandardFontLoadPrincipal(); +} + +void gfxUserFontEntry::GetFamilyNameAndURIForLogging(uint32_t aSrcIndex, + nsACString& aFamilyName, + nsACString& aURI) { + aFamilyName = mFamilyName; + + aURI.Truncate(); + if (aSrcIndex >= mSrcList.Length()) { + aURI.AppendLiteral("(end of source list)"); + } else { + if (mSrcList[aSrcIndex].mURI) { + mSrcList[aSrcIndex].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 <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() { + NS_ASSERTION(mCurrentSrcIndex < 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 + mCurrentSrcIndex++; + } + + DoLoadNextSrc(false); +} + +void gfxUserFontEntry::ContinueLoad() { + MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING); + MOZ_ASSERT(mSrcList[mCurrentSrcIndex].mSourceType == + gfxFontFaceSrc::eSourceType_URL); + + SetLoadState(STATUS_LOADING); + DoLoadNextSrc(/* aIsContinue = */ true); + if (LoadState() != STATUS_LOADING) { + MOZ_ASSERT(mUserFontLoadState != STATUS_LOAD_PENDING, + "Not in parallel traversal, shouldn't get LOAD_PENDING again"); + // Loading is synchronously finished (loaded from cache or failed). We + // need to increment the generation so that we flush the style data to + // use the new loaded font face. + // Without parallel traversal, we would simply get the right font data + // after the first call to DoLoadNextSrc() in this case, so we don't need + // to touch the generation to trigger another restyle. + // XXX We may want to return synchronously in parallel traversal in those + // cases as well if possible, so that we don't have an additional restyle. + // That doesn't work currently because Document::GetDocShell (called from + // FontFaceSet::CheckFontLoad) dereferences a weak pointer, which is not + // allowed in parallel traversal. + IncrementGeneration(); + } +} + +static bool IgnorePrincipal(gfxFontSrcURI* aURI) { + return aURI->InheritsSecurityContext(); +} + +void gfxUserFontEntry::DoLoadNextSrc(bool aIsContinue) { + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (NS_WARN_IF(!fontSet)) { + LOG(("userfonts (%p) failed expired font set for (%s)\n", fontSet.get(), + mFamilyName.get())); + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); + return; + } + + uint32_t numSrc = mSrcList.Length(); + + // load each src entry in turn, until a local face is found + // or a download begins successfully + while (mCurrentSrcIndex < numSrc) { + gfxFontFaceSrc& currSrc = mSrcList[mCurrentSrcIndex]; + + // src local ==> lookup and load immediately + + if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + pfl->AddUserFontSet(fontSet); + // Don't look up local fonts if the font whitelist is being used. + gfxFontEntry* fe = nullptr; + if (!pfl->IsFontFamilyWhitelistActive()) { + fe = gfxPlatform::GetPlatform()->LookupLocalFont( + fontSet->GetPresContext(), currSrc.mLocalName, Weight(), Stretch(), + SlantStyle()); + // Note that we've attempted a local lookup, even if it failed, + // as this means we are dependent on any updates to the font list. + mSeenLocalSource = true; + nsTArray<RefPtr<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", + fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(), + mFamilyName.get(), uint32_t(fontSet->mGeneration))); + fe->mFeatureSettings.AppendElements(mFeatureSettings); + fe->mVariationSettings.AppendElements(mVariationSettings); + fe->mLanguageOverride = mLanguageOverride; + fe->mFamilyName = mFamilyName; + fe->mRangeFlags = mRangeFlags; + fe->mAscentOverride = mAscentOverride; + fe->mDescentOverride = mDescentOverride; + fe->mLineGapOverride = mLineGapOverride; + fe->mSizeAdjust = mSizeAdjust; + // 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, mCurrentSrcIndex, false, nsCString(), nullptr, 0, + gfxUserFontData::kUnknownCompression); + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } + LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", + fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(), + mFamilyName.get())); + } + + // src url ==> start the load process + else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) { + if (gfxPlatform::GetPlatform()->IsFontFormatSupported( + currSrc.mFormatHint, currSrc.mTechFlags)) { + if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { + // Only support style worker threads synchronously getting + // entries from the font cache when it's not a data: URI + // @font-face that came from UA or user sheets, since we + // were not able to call IsFontLoadAllowed ahead of time + // for these entries. + if (currSrc.mUseOriginPrincipal && IgnorePrincipal(currSrc.mURI)) { + set->AppendTask(PostTraversalTask::LoadFontEntry(this)); + SetLoadState(STATUS_LOAD_PENDING); + return; + } + } + + // see if we have an existing entry for this source + gfxFontEntry* fe = + gfxUserFontSet::UserFontCache::GetFont(currSrc, *this); + if (fe) { + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + LOG( + ("userfonts (%p) [src %d] " + "loaded uri from cache: (%s) for (%s)\n", + fontSet.get(), mCurrentSrcIndex, + currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get())); + return; + } + + if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { + // If we need to start a font load and we're on a style + // worker thread, we have to defer it. + set->AppendTask(PostTraversalTask::LoadFontEntry(this)); + SetLoadState(STATUS_LOAD_PENDING); + return; + } + + // record the principal we should use for the load for use when + // creating a channel and when caching the loaded entry. + mPrincipal = currSrc.LoadPrincipal(*fontSet); + + const bool loadDoesntSpin = + !aIsContinue && currSrc.mURI->SyncLoadIsOK(); + if (loadDoesntSpin) { + uint8_t* buffer = nullptr; + uint32_t bufferLength = 0; + + // sync load font immediately + nsresult rv = + fontSet->SyncLoadFontData(this, &currSrc, buffer, bufferLength); + + if (NS_SUCCEEDED(rv) && + LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) { + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } + fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed", + nsIScriptError::errorFlag, rv); + } else if (!aIsContinue) { + RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "gfxUserFontSet::AsyncContinueLoad", + [loader = RefPtr{this}] { loader->ContinueLoad(); }); + SetLoadState(STATUS_LOAD_PENDING); + // We don't want to trigger the channel open at random points in + // time, because it can run privileged JS. + if (!nsContentUtils::IsSafeToRunScript()) { + // There's a script-blocker on the stack. We know the sooner point + // where we can trigger the load. + nsContentUtils::AddScriptRunner(runnable.forget()); + } else { + // We dispatch with a rather high priority, since somebody actually + // cares about this font. + NS_DispatchToCurrentThreadQueue(runnable.forget(), + EventQueuePriority::MediumHigh); + } + return; + } else { + // Actually start the async load. + nsresult rv = fontSet->StartLoad(this, mCurrentSrcIndex); + if (NS_SUCCEEDED(rv)) { + LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", + fontSet.get(), mCurrentSrcIndex, + currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get())); + return; + } + fontSet->LogMessage(this, mCurrentSrcIndex, + "failed to start download", + nsIScriptError::errorFlag, rv); + } + } else { + // We don't log a warning to the web console yet, + // as another source may load successfully + mUnsupportedFormat = true; + } + } else { + // FontFace buffer ==> load immediately + 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 && + LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) { + // LoadPlatformFontSync takes ownership of the buffer, so no need + // to free it here. + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } + fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed", + nsIScriptError::errorFlag); + } + + mCurrentSrcIndex++; + } + + if (mUnsupportedFormat) { + fontSet->LogMessage(this, mCurrentSrcIndex, "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", fontSet.get(), + 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::LoadPlatformFontSync(uint32_t aSrcIndex, + const uint8_t* aFontData, + uint32_t aLength) { + AUTO_PROFILER_LABEL("gfxUserFontEntry::LoadPlatformFontSync", OTHER); + 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"); + + // Unwrap/decompress/sanitize or otherwise munge the downloaded data + // to make a usable sfnt structure. + + // Call the OTS sanitizer; this will also decode WOFF to sfnt + // if necessary. The original data in aFontData is left unchanged. + uint32_t sanitaryLen; + gfxUserFontType fontType; + nsTArray<OTSMessage> messages; + const uint8_t* sanitaryData = + SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages); + + return LoadPlatformFont(aSrcIndex, aFontData, aLength, fontType, sanitaryData, + sanitaryLen, std::move(messages)); +} + +void gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) { + MOZ_ASSERT(!NS_IsMainThread()); + + uint32_t sanitaryLen; + gfxUserFontType fontType; + nsTArray<OTSMessage> messages; + const uint8_t* sanitaryData = + SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages); + + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<uint32_t, const uint8_t*, uint32_t, gfxUserFontType, + const uint8_t*, uint32_t, nsTArray<OTSMessage>&&, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>( + "gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread", this, + &gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread, aSrcIndex, + aFontData, aLength, fontType, sanitaryData, sanitaryLen, + std::move(messages), aCallback); + NS_DispatchToMainThread(event.forget()); +} + +bool gfxUserFontEntry::LoadPlatformFont(uint32_t aSrcIndex, + const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, + gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (NS_WARN_IF(!fontSet)) { + free((void*)aOriginalFontData); + free((void*)aSanitizedFontData); + return false; + } + + for (const auto& msg : aMessages) { + fontSet->LogMessage(this, aSrcIndex, msg.mMessage.get(), + msg.mLevel > 0 ? nsIScriptError::warningFlag + : nsIScriptError::errorFlag); + } + + if (!aSanitizedFontData) { + fontSet->LogMessage(this, aSrcIndex, "rejected by sanitizer"); + } else { + // Check whether aSanitizedFontData 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( + aSanitizedFontData, aSanitizedLength) != GFX_USERFONT_OPENTYPE) { + fontSet->LogMessage(this, aSrcIndex, "not a supported OpenType format"); + free((void*)aSanitizedFontData); + aSanitizedFontData = nullptr; + } + } + + // 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 InspectorUtils API. + nsAutoCString originalFullName; + + gfxFontEntry* fe = nullptr; + uint32_t fontCompressionRatio = 0; + size_t computedSize = 0; + + if (aSanitizedFontData) { + if (aSanitizedLength) { + fontCompressionRatio = + uint32_t(100.0 * aOriginalLength / aSanitizedLength + 0.5); + if (aFontType == GFX_USERFONT_WOFF || aFontType == GFX_USERFONT_WOFF2) { + Telemetry::Accumulate(aFontType == 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(aSanitizedFontData, aSanitizedLength, + 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(aSanitizedFontData); + + // Here ownership of aSanitizedFontData is passed to the platform, + // which will delete it when no longer required + fe = gfxPlatform::GetPlatform()->MakePlatformFont( + mName, Weight(), Stretch(), SlantStyle(), aSanitizedFontData, + aSanitizedLength); + if (!fe) { + fontSet->LogMessage(this, aSrcIndex, "not usable by platform"); + } + } + + if (fe) { + fe->mComputedSizeOfUserFont = computedSize; + + // Save a copy of the metadata block (if present) for InspectorUtils + // 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 (aFontType == GFX_USERFONT_WOFF) { + CopyWOFFMetadata<WOFFHeader>(aOriginalFontData, aOriginalLength, + &metadata, &metaOrigLen); + compression = gfxUserFontData::kZlibCompression; + } else if (aFontType == GFX_USERFONT_WOFF2) { + CopyWOFFMetadata<WOFF2Header>(aOriginalFontData, aOriginalLength, + &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->mVariationSettings.AppendElements(mVariationSettings); + fe->mLanguageOverride = mLanguageOverride; + fe->mFamilyName = mFamilyName; + fe->mRangeFlags = mRangeFlags; + fe->mAscentOverride = mAscentOverride; + fe->mDescentOverride = mDescentOverride; + fe->mLineGapOverride = mLineGapOverride; + fe->mSizeAdjust = mSizeAdjust; + StoreUserFontData(fe, aSrcIndex, fontSet->GetPrivateBrowsing(), + originalFullName, &metadata, metaOrigLen, compression); + LOG( + ("userfonts (%p) [src %d] loaded uri: (%s) for (%s) " + "(%p) gen: %8.8x compress: %d%%\n", + fontSet.get(), aSrcIndex, + mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get(), + this, uint32_t(fontSet->mGeneration), fontCompressionRatio)); + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + gfxUserFontSet::UserFontCache::CacheFont(fe); + } else { + LOG(( + "userfonts (%p) [src %d] failed uri: (%s) for (%s)" + " error making platform font\n", + fontSet.get(), aSrcIndex, + mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get())); + } + + // The downloaded data can now be discarded; the font entry is using the + // sanitized copy + free((void*)aOriginalFontData); + + return fe != nullptr; +} + +void gfxUserFontEntry::Load() { + if (mUserFontLoadState != STATUS_NOT_LOADED) { + return; + } + if (dom::IsCurrentThreadRunningWorker()) { + // TODO: Maybe support loading the font entry in workers, at least for + // buffers or other sync sources? + NS_DispatchToMainThread(NewRunnableMethod("gfxUserFontEntry::Load", this, + &gfxUserFontEntry::Load)); + return; + } + LoadNextSrc(); +} + +void gfxUserFontEntry::IncrementGeneration() { + nsTArray<RefPtr<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(). +void gfxUserFontEntry::FontDataDownloadComplete( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsresult aDownloadStatus, nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // 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) { + LoadPlatformFontAsync(aSrcIndex, aFontData, aLength, aCallback); + return; + } + + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (fontSet) { + // download failed or font-display timeout passed + if (mFontDataLoadingState == LOADING_TIMED_OUT) { + fontSet->LogMessage(this, aSrcIndex, + "font-display timeout, webfont not used", + nsIScriptError::infoFlag, aDownloadStatus); + } else { + fontSet->LogMessage(this, aSrcIndex, "download failed", + nsIScriptError::errorFlag, aDownloadStatus); + } + } + + if (aFontData) { + free((void*)aFontData); + } + + FontLoadFailed(aCallback); +} + +void gfxUserFontEntry::LoadPlatformFontAsync( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback) { + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> cb( + new nsMainThreadPtrHolder<nsIFontLoadCompleteCallback>("FontLoader", + aCallback)); + + // Do the OpenType sanitization over on the font loading thread. Once that is + // complete, we'll continue in ContinuePlatformFontLoadOnMainThread. + // + // We hold a strong reference to the gfxUserFontSet during this work, since + // the document might be closed while we are OMT, and release it at the end + // of ContinuePlatformFontLoadOnMainThread. + // + // If the set has already been freed, then the loading will fail when we + // resume on the main thread. + + MOZ_ASSERT(!mLoadingFontSet); + mLoadingFontSet = GetUserFontSet(); + + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<uint32_t, const uint8_t*, uint32_t, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>( + "gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread", this, + &gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread, aSrcIndex, + aFontData, aLength, cb); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(event.forget())); +} + +void gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread( + uint32_t aSrcIndex, const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + bool loaded = LoadPlatformFont(aSrcIndex, aOriginalFontData, aOriginalLength, + aFontType, aSanitizedFontData, + aSanitizedLength, std::move(aMessages)); + aOriginalFontData = nullptr; + aSanitizedFontData = nullptr; + + if (loaded) { + IncrementGeneration(); + aCallback->FontLoadComplete(); + } else { + FontLoadFailed(aCallback); + } + + // Set in LoadPlatformFontAsync. If it is null, then the font set should have + // already been freed and we would not succeed in loading the font. + MOZ_ASSERT_IF(loaded, mLoadingFontSet); + mLoadingFontSet = nullptr; +} + +void gfxUserFontEntry::FontLoadFailed(nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // Error occurred. Make sure the FontFace's promise is rejected if the + // load timed out, or else load the next src. + if (mFontDataLoadingState == LOADING_TIMED_OUT) { + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); + } else { + 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(); + aCallback->FontLoadComplete(); +} + +void gfxUserFontEntry::GetUserFontSets( + nsTArray<RefPtr<gfxUserFontSet>>& aResult) { + aResult.Clear(); + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (fontSet) { + aResult.AppendElement(std::move(fontSet)); + } +} + +gfxUserFontSet::gfxUserFontSet() + : mFontFamilies(4), + mRebuildGeneration(0), + mLocalRulesUsed(false), + mRebuildLocalRules(false), + mDownloadCount(0), + mDownloadSize(0) { + IncrementGeneration(true); +} + +gfxUserFontSet::~gfxUserFontSet() { Destroy(); } + +void gfxUserFontSet::Destroy() { + if (auto* pfl = gfxPlatformFontList::PlatformFontList(false)) { + pfl->RemoveUserFontSet(this); + } + + mFontFamilies.Clear(); +} + +already_AddRefed<gfxUserFontEntry> gfxUserFontSet::FindOrCreateUserFontEntry( + nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr) { + 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. + RefPtr<gfxUserFontFamily> family = LookupFamily(aAttr.mFamilyName); + if (family) { + entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aAttr); + } + + if (!entry) { + entry = CreateUserFontEntry(std::move(aFontFaceSrcList), std::move(aAttr)); + } + + return entry.forget(); +} + +gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry( + gfxUserFontFamily* aFamily, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + const gfxUserFontAttributes& aAttr) { + aFamily->ReadLock(); + const auto& fontList = aFamily->GetFontList(); + gfxUserFontEntry* result = nullptr; + + for (const auto& font : fontList) { + if (!font->mIsUserFontContainer) { + continue; + } + + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(font.get()); + if (ufe->Matches(aFontFaceSrcList, aAttr)) { + result = ufe; + break; + } + } + aFamily->ReadUnlock(); + + return result; +} + +void gfxUserFontSet::AddUserFontEntry(const nsCString& aFamilyName, + gfxUserFontEntry* aUserFontEntry) { + RefPtr<gfxUserFontFamily> family = GetFamily(aFamilyName); + family->AddFontEntry(aUserFontEntry); + + if (LOG_ENABLED()) { + nsAutoCString weightString; + aUserFontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + aUserFontEntry->Stretch().ToString(stretchString); + LOG( + ("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s " + "stretch: %s display: %d", + this, aFamilyName.get(), aUserFontEntry, + (aUserFontEntry->IsItalic() + ? "italic" + : (aUserFontEntry->IsOblique() ? "oblique" : "normal")), + weightString.get(), stretchString.get(), + static_cast<int>(aUserFontEntry->GetFontDisplay()))); + } +} + +void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) { + // add one, increment again if zero + do { + mGeneration = ++sFontSetGeneration; + } while (mGeneration == 0); + if (aIsRebuild) { + mRebuildGeneration = mGeneration; + } +} + +void gfxUserFontSet::RebuildLocalRules() { + if (mLocalRulesUsed) { + mRebuildLocalRules = true; + DoRebuildUserFontSet(); + } +} + +already_AddRefed<gfxUserFontFamily> gfxUserFontSet::LookupFamily( + const nsACString& aFamilyName) const { + nsAutoCString key(aFamilyName); + ToLowerCase(key); + + return mFontFamilies.Get(key); +} + +already_AddRefed<gfxUserFontFamily> gfxUserFontSet::GetFamily( + const nsACString& aFamilyName) { + nsAutoCString key(aFamilyName); + ToLowerCase(key); + + return do_AddRef(mFontFamilies.GetOrInsertNew(key, aFamilyName)); +} + +void gfxUserFontSet::ForgetLocalFaces() { + for (const auto& fam : mFontFamilies.Values()) { + ForgetLocalFace(fam); + } +} + +void gfxUserFontSet::ForgetLocalFace(gfxUserFontFamily* aFontFamily) { + aFontFamily->ReadLock(); + const auto& fonts = aFontFamily->GetFontList(); + for (const auto& f : fonts) { + auto ufe = static_cast<gfxUserFontEntry*>(f.get()); + // If the user font entry has loaded an entry using src:local(), + // discard it as no longer valid. + if (ufe->GetPlatformFontEntry() && + ufe->GetPlatformFontEntry()->IsLocalUserFont()) { + ufe->mPlatformFontEntry = nullptr; + } + // We need to re-evaluate the source list in the context of the new + // platform fontlist, whether or not the entry actually used a local() + // source last time, as one might be newly available. + if (ufe->mSeenLocalSource) { + ufe->LoadCanceled(); + } + } + aFontFamily->ReadUnlock(); +} + +/////////////////////////////////////////////////////////////////////////////// +// 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()) { + 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 { + MOZ_ASSERT_UNREACHABLE("unexpected topic"); + } + + return NS_OK; +} + +bool gfxUserFontSet::UserFontCache::Entry::KeyEquals( + const KeyTypePointer aKey) const { + const gfxFontEntry* fe = aKey->mFontEntry; + + if (!mURI->Equals(aKey->mURI)) { + 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 (!mPrincipal->Equals(aKey->mPrincipal)) { + return false; + } + } + + if (mPrivate != aKey->mPrivate) { + return false; + } + + if (mFontEntry->SlantStyle() != fe->SlantStyle() || + mFontEntry->Weight() != fe->Weight() || + mFontEntry->Stretch() != fe->Stretch() || + mFontEntry->mRangeFlags != fe->mRangeFlags || + mFontEntry->mFeatureSettings != fe->mFeatureSettings || + mFontEntry->mVariationSettings != fe->mVariationSettings || + mFontEntry->mLanguageOverride != fe->mLanguageOverride || + mFontEntry->mAscentOverride != fe->mAscentOverride || + mFontEntry->mDescentOverride != fe->mDescentOverride || + mFontEntry->mLineGapOverride != fe->mLineGapOverride || + mFontEntry->mSizeAdjust != fe->mSizeAdjust || + mFontEntry->mFamilyName != fe->mFamilyName) { + return false; + } + + return true; +} + +void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry) { + 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()); + } + + // 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. + gfxFontSrcPrincipal* principal; + if (IgnorePrincipal(data->mURI)) { + principal = nullptr; + } else { + principal = data->mPrincipal; + } + sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate)); + +#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( + const gfxFontFaceSrc& aSrc, const gfxUserFontEntry& aUserFontEntry) { + if (!sUserFonts || + Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { + return nullptr; + } + + RefPtr<gfxUserFontSet> srcFontSet = aUserFontEntry.GetUserFontSet(); + if (NS_WARN_IF(!srcFontSet) || srcFontSet->BypassCache()) { + return nullptr; + } + + // Ignore principal when looking up a data: URI. + RefPtr<gfxFontSrcPrincipal> principal = + IgnorePrincipal(aSrc.mURI) ? nullptr : aSrc.LoadPrincipal(*srcFontSet); + + Entry* entry = sUserFonts->GetEntry( + Key(aSrc.mURI, principal, const_cast<gfxUserFontEntry*>(&aUserFontEntry), + srcFontSet->GetPrivateBrowsing())); + if (!entry) { + 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 (!srcFontSet->IsFontLoadAllowed(aSrc)) { + return nullptr; + } + + return entry->GetFontEntry(); +} + +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 { + path.AppendPrintf("family=%s", mFontEntry->mFamilyName.get()); + if (mURI) { + nsCString spec = mURI->GetSpecOrDefault(); + spec.ReplaceChar('/', '\\'); + // Some fonts are loaded using horrendously-long data: URIs; + // truncate those before reporting them. + if (mURI->get()->SchemeIs("data") && spec.Length() > 255) { + spec.Truncate(252); + spec.AppendLiteral("..."); + } + path.AppendPrintf(", url=%s", spec.get()); + } + if (mPrincipal) { + nsAutoCString spec; + mPrincipal->NodePrincipal()->GetAsciiSpec(spec); + 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( + ""_ns, path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf), + "Memory used by @font-face resource."_ns, 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->NodePrincipal()->GetURI(getter_AddRefs(principalURI)); + if (NS_SUCCEEDED(rv)) { + principalURI->GetSpec(principalURISpec); + } + + nsCOMPtr<nsIURI> domainURI; + mPrincipal->NodePrincipal()->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, mURI->Hash(), 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 |