From a294160dcc361af254b59f50cb9e7fc28fc4e6d0 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Mon, 10 Dec 2012 09:31:07 +0000 Subject: [PATCH] bug 816483 - cache instantiated user fonts and share them across pages that use the same resources. r=roc --- gfx/thebes/gfxFT2FontList.cpp | 1 + gfx/thebes/gfxFont.cpp | 13 ++- gfx/thebes/gfxPangoFonts.cpp | 16 ++- gfx/thebes/gfxUserFontSet.cpp | 180 ++++++++++++++++++++++++------ gfx/thebes/gfxUserFontSet.h | 122 ++++++++++++++++++-- layout/style/nsFontFaceLoader.cpp | 33 ++---- layout/style/nsFontFaceLoader.h | 5 +- 7 files changed, 294 insertions(+), 76 deletions(-) diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp index a057054ee194..ac9f8347aa17 100644 --- a/gfx/thebes/gfxFT2FontList.cpp +++ b/gfx/thebes/gfxFT2FontList.cpp @@ -196,6 +196,7 @@ FT2FontEntry::CreateFontEntry(const gfxProxyFontEntry &aProxyEntry, fe->mItalic = aProxyEntry.mItalic; fe->mWeight = aProxyEntry.mWeight; fe->mStretch = aProxyEntry.mStretch; + fe->mIsUserFont = true; } return fe; } diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 2a5ad2bc4cb3..5694598511f5 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -82,8 +82,14 @@ gfxCharacterMap::NotifyReleased() delete this; } -gfxFontEntry::~gfxFontEntry() +gfxFontEntry::~gfxFontEntry() { + // For downloaded fonts, we need to tell the user font cache that this + // entry is being deleted. + if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) { + gfxUserFontSet::UserFontCache::ForgetFont(this); + } + if (mSVGGlyphs) { delete mSVGGlyphs; } @@ -1238,6 +1244,11 @@ gfxFontCache::gfxFontCache() gfxFontCache::~gfxFontCache() { + // Ensure the user font cache releases its references to font entries, + // so they aren't kept alive after the font instances and font-list + // have been shut down. + gfxUserFontSet::UserFontCache::Shutdown(); + if (mWordCacheExpirationTimer) { mWordCacheExpirationTimer->Cancel(); mWordCacheExpirationTimer = nullptr; diff --git a/gfx/thebes/gfxPangoFonts.cpp b/gfx/thebes/gfxPangoFonts.cpp index ec7a8c14f28d..e8619bb7fb2f 100644 --- a/gfx/thebes/gfxPangoFonts.cpp +++ b/gfx/thebes/gfxPangoFonts.cpp @@ -1504,6 +1504,15 @@ gfxFcFontSet::SortPreferredFonts(bool &aWaitForUserFont) for (uint32_t f = 0; f < familyFonts->Length(); ++f) { FcPattern *font = familyFonts->ElementAt(f); + // Fix up the family name of user-font patterns, as the same + // font entry may be used (via the UserFontCache) for multiple + // CSS family names + if (isUserFont) { + font = FcPatternDuplicate(font); + FcPatternDel(font, FC_FAMILY); + FcPatternAddString(font, FC_FAMILY, family); + } + // User fonts are already filtered by slant (but not size) in // mUserFontSet->FindFontEntry(). if (!isUserFont && !SlantIsAcceptable(font, requestedSlant)) @@ -1524,7 +1533,12 @@ gfxFcFontSet::SortPreferredFonts(bool &aWaitForUserFont) // FcFontSetDestroy will remove a reference but FcFontSetAdd // does _not_ take a reference! if (FcFontSetAdd(fontSet, font)) { - FcPatternReference(font); + // We don't add a reference here for user fonts, because we're + // using a local clone of the pattern (see above) in order to + // override the family name + if (!isUserFont) { + FcPatternReference(font); + } } } } diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp index f6586a9eb6f1..0197f36ffb2f 100644 --- a/gfx/thebes/gfxUserFontSet.cpp +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -15,6 +15,8 @@ #include "prlong.h" #include "nsNetUtil.h" #include "nsIProtocolHandler.h" +#include "nsIPrincipal.h" +#include "mozilla/Telemetry.h" #include "woff.h" @@ -406,6 +408,7 @@ StoreUserFontData(gfxFontEntry* aFontEntry, gfxProxyFontEntry* aProxy, userFontData->mLocalName = src.mLocalName; } else { userFontData->mURI = src.mURI; + userFontData->mPrincipal = aProxy->mPrincipal; } userFontData->mFormat = src.mFormatFlags; userFontData->mRealName = aOriginalName; @@ -559,48 +562,65 @@ gfxUserFontSet::LoadNext(gfxProxyFontEntry *aProxyEntry) if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI, currSrc.mFormatFlags)) { - nsresult rv; - bool loadDoesntSpin = false; - rv = NS_URIChainHasFlags(currSrc.mURI, - nsIProtocolHandler::URI_SYNC_LOAD_IS_OK, - &loadDoesntSpin); + nsIPrincipal *principal = nullptr; + nsresult rv = CheckFontLoad(&currSrc, &principal); - if (NS_SUCCEEDED(rv) && loadDoesntSpin) { - uint8_t *buffer = nullptr; - uint32_t bufferLength = 0; - - // sync load font immediately - rv = SyncLoadFontData(aProxyEntry, &currSrc, buffer, - bufferLength); - - if (NS_SUCCEEDED(rv) && - LoadFont(aProxyEntry, buffer, bufferLength)) { + if (NS_SUCCEEDED(rv) && principal != nullptr) { + // see if we have an existing entry for this source + gfxFontEntry *fe = + UserFontCache::GetFont(currSrc.mURI, principal, + aProxyEntry); + if (fe) { + ReplaceFontEntry(aProxyEntry, fe); return STATUS_LOADED; - } else { - LogMessage(aProxyEntry, "font load failed", - nsIScriptError::errorFlag, rv); } - } else { - // otherwise load font async - rv = StartLoad(aProxyEntry, &currSrc); - bool loadOK = NS_SUCCEEDED(rv); + // record the principal returned by CheckFontLoad, + // for use when creating a channel + // and when caching the loaded entry + aProxyEntry->mPrincipal = principal; - if (loadOK) { -#ifdef PR_LOGGING - if (LOG_ENABLED()) { - nsAutoCString fontURI; - currSrc.mURI->GetSpec(fontURI); - LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", - this, aProxyEntry->mSrcIndex, fontURI.get(), - NS_ConvertUTF16toUTF8(aProxyEntry->mFamily->Name()).get())); + 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 = SyncLoadFontData(aProxyEntry, &currSrc, + buffer, bufferLength); + if (NS_SUCCEEDED(rv) && + (fe = LoadFont(aProxyEntry, buffer, bufferLength))) { + UserFontCache::CacheFont(fe); + return STATUS_LOADED; + } else { + LogMessage(aProxyEntry, "font load failed", + nsIScriptError::errorFlag, rv); } -#endif - return STATUS_LOADING; } else { - LogMessage(aProxyEntry, "download failed", - nsIScriptError::errorFlag, rv); + // otherwise load font async + rv = StartLoad(aProxyEntry, &currSrc); + if (NS_SUCCEEDED(rv)) { +#ifdef PR_LOGGING + if (LOG_ENABLED()) { + nsAutoCString fontURI; + currSrc.mURI->GetSpec(fontURI); + LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", + this, aProxyEntry->mSrcIndex, fontURI.get(), + NS_ConvertUTF16toUTF8(aProxyEntry->mFamily->Name()).get())); + } +#endif + return STATUS_LOADING; + } else { + LogMessage(aProxyEntry, "download failed", + nsIScriptError::errorFlag, rv); + } } + } else { + LogMessage(aProxyEntry, "download not allowed", + nsIScriptError::errorFlag, rv); } } else { // We don't log a warning to the web console yet, @@ -752,6 +772,7 @@ gfxUserFontSet::LoadFont(gfxProxyFontEntry *aProxy, uint32_t(mGeneration))); } #endif + UserFontCache::CacheFont(fe); ReplaceFontEntry(aProxy, fe); } else { #ifdef PR_LOGGING @@ -778,3 +799,94 @@ gfxUserFontSet::GetFamily(const nsAString& aFamilyName) const return mFontFamilies.GetWeak(key); } +/////////////////////////////////////////////////////////////////////////////// +// 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; + +bool +gfxUserFontSet::UserFontCache::Entry::KeyEquals(const KeyTypePointer aKey) const +{ + bool equal; + if (NS_FAILED(mURI->Equals(aKey->mURI, &equal)) || !equal) { + return false; + } + + if (NS_FAILED(mPrincipal->Equals(aKey->mPrincipal, &equal)) || !equal) { + return false; + } + + const gfxFontEntry *fe = aKey->mFontEntry; + if (mFontEntry->mItalic != fe->mItalic || + mFontEntry->mWeight != fe->mWeight || + mFontEntry->mStretch != fe->mStretch || + mFontEntry->mFeatureSettings != fe->mFeatureSettings || + mFontEntry->mLanguageOverride != fe->mLanguageOverride) { + return false; + } + + return true; +} + +void +gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry *aFontEntry) +{ + if (!sUserFonts) { + sUserFonts = new nsTHashtable; + sUserFonts->Init(); + } + + gfxUserFontData *data = aFontEntry->mUserFontData; + sUserFonts->PutEntry(Key(data->mURI, data->mPrincipal, aFontEntry)); +} + +void +gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry *aFontEntry) +{ + if (!sUserFonts) { + // if we've already deleted the cache (i.e. during shutdown), + // just ignore this + return; + } + + gfxUserFontData *data = aFontEntry->mUserFontData; + sUserFonts->RemoveEntry(Key(data->mURI, data->mPrincipal, aFontEntry)); +} + +gfxFontEntry* +gfxUserFontSet::UserFontCache::GetFont(nsIURI *aSrcURI, + nsIPrincipal *aPrincipal, + gfxProxyFontEntry *aProxy) +{ + if (!sUserFonts) { + return nullptr; + } + + Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, aPrincipal, aProxy)); + if (entry) { + return entry->GetFontEntry(); + } + + return nullptr; +} + +void +gfxUserFontSet::UserFontCache::Shutdown() +{ + if (sUserFonts) { + delete sUserFonts; + sUserFonts = nullptr; + } +} diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h index e589a6108c9c..187f6e3cd262 100644 --- a/gfx/thebes/gfxUserFontSet.h +++ b/gfx/thebes/gfxUserFontSet.h @@ -14,10 +14,11 @@ #include "nsCOMPtr.h" #include "nsIURI.h" #include "nsIFile.h" +#include "nsIPrincipal.h" #include "nsISupportsImpl.h" #include "nsIScriptError.h" +#include "nsURIHashKey.h" -class nsIURI; class gfxMixedFontFamily; class nsFontFaceLoader; @@ -35,10 +36,9 @@ struct gfxFontFaceSrc { uint32_t mFormatFlags; nsString mLocalName; // full font name if local - nsCOMPtr mURI; // uri if url + nsCOMPtr mURI; // uri if url nsCOMPtr mReferrer; // referrer url if url - nsCOMPtr mOriginPrincipal; // principal if url - + nsCOMPtr mOriginPrincipal; // principal if url }; // Subclassed to store platform-specific code cleaned out when font entry is @@ -56,6 +56,7 @@ public: nsTArray mMetadata; // woff metadata block (compressed), if any nsCOMPtr mURI; // URI of the source, if it was url() + nsCOMPtr mPrincipal; // principal for the download, if url() nsString mLocalName; // font name used for the source, if local() nsString mRealName; // original fullname from the font resource uint32_t mSrcIndex; // index in the rule's source list @@ -202,10 +203,14 @@ public: bool& aFoundFamily, bool& aNeedsBold, bool& aWaitForUserFont); - + + // check whether the given source is allowed to be loaded + virtual nsresult CheckFontLoad(const gfxFontFaceSrc *aFontFaceSrc, + nsIPrincipal **aPrincipal) = 0; + // initialize the process that loads external font data, which upon // completion will call OnLoadComplete method - virtual nsresult StartLoad(gfxProxyFontEntry *aProxy, + virtual nsresult StartLoad(gfxProxyFontEntry *aProxy, const gfxFontFaceSrc *aFontFaceSrc) = 0; // when download has been completed, pass back data here @@ -231,6 +236,104 @@ public: // increment the generation on font load void IncrementGeneration(); + class UserFontCache { + public: + // Record a loaded user-font in the cache. This requires that the + // font-entry's userFontData has been set up already, as it relies + // on the URI and Principal recorded there. + static void CacheFont(gfxFontEntry *aFontEntry); + + // The given gfxFontEntry is being destroyed, so remove any record that + // refers to it. + static void ForgetFont(gfxFontEntry *aFontEntry); + + // Return the gfxFontEntry corresponding to a given URI and principal, + // and the features of the given proxy, or nullptr if none is available + static gfxFontEntry* GetFont(nsIURI *aSrcURI, + nsIPrincipal *aPrincipal, + gfxProxyFontEntry *aProxy); + + // Clear everything so that we don't leak URIs and Principals. + static void Shutdown(); + + private: + // Key used to look up entries in the user-font cache. + // Note that key comparison does *not* use the mFontEntry field + // as a whole; it only compares specific fields within the entry + // (weight/width/style/features) that could affect font selection + // or rendering, and that must match between a font-set's proxy + // entry and the corresponding "real" font entry. + struct Key { + nsCOMPtr mURI; + nsCOMPtr mPrincipal; + gfxFontEntry *mFontEntry; + + Key(nsIURI* aURI, nsIPrincipal* aPrincipal, + gfxFontEntry* aFontEntry) + : mURI(aURI), + mPrincipal(aPrincipal), + mFontEntry(aFontEntry) + { } + }; + + class Entry : public PLDHashEntryHdr { + public: + typedef const Key& KeyType; + typedef const Key* KeyTypePointer; + + Entry(KeyTypePointer aKey) + : mURI(aKey->mURI), + mPrincipal(aKey->mPrincipal), + mFontEntry(aKey->mFontEntry) + { } + + Entry(const Entry& aOther) + : mURI(aOther.mURI), + mPrincipal(aOther.mPrincipal), + mFontEntry(aOther.mFontEntry) + { } + + ~Entry() { } + + bool KeyEquals(const KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + uint32_t principalHash; + aKey->mPrincipal->GetHashValue(&principalHash); + return mozilla::HashGeneric(principalHash, + nsURIHashKey::HashKey(aKey->mURI), + HashFeatures(aKey->mFontEntry->mFeatureSettings), + ( aKey->mFontEntry->mItalic | + (aKey->mFontEntry->mWeight << 1) | + (aKey->mFontEntry->mStretch << 10) ) ^ + aKey->mFontEntry->mLanguageOverride); + } + + enum { ALLOW_MEMMOVE = false }; + + gfxFontEntry* GetFontEntry() const { return mFontEntry; } + + private: + static uint32_t + HashFeatures(const nsTArray& aFeatures) { + return mozilla::HashBytes(aFeatures.Elements(), + aFeatures.Length() * sizeof(gfxFontFeature)); + } + + nsCOMPtr mURI; + nsCOMPtr mPrincipal; + + // The "real" font entry corresponding to this downloaded font. + // The font entry MUST notify the cache when it is destroyed + // (by calling Forget()). + gfxFontEntry *mFontEntry; + }; + + static nsTHashtable *sUserFonts; + }; + protected: // for a given proxy font entry, attempt to load the next resource // in the src list @@ -247,11 +350,7 @@ protected: virtual nsresult SyncLoadFontData(gfxProxyFontEntry *aFontToLoad, const gfxFontFaceSrc *aFontFaceSrc, uint8_t* &aBuffer, - uint32_t &aBufferLength) - { - // implemented in nsUserFontSet - return NS_ERROR_NOT_IMPLEMENTED; - } + uint32_t &aBufferLength) = 0; gfxMixedFontFamily *GetFamily(const nsAString& aName) const; @@ -320,6 +419,7 @@ public: nsTArray mSrcList; uint32_t mSrcIndex; // index of loading src item nsFontFaceLoader *mLoader; // current loader for this entry, if any + nsCOMPtr mPrincipal; }; diff --git a/layout/style/nsFontFaceLoader.cpp b/layout/style/nsFontFaceLoader.cpp index dc34df10dbdd..419d47bb88ec 100644 --- a/layout/style/nsFontFaceLoader.cpp +++ b/layout/style/nsFontFaceLoader.cpp @@ -316,10 +316,6 @@ nsUserFontSet::StartLoad(gfxProxyFontEntry *aProxy, const gfxFontFaceSrc *aFontFaceSrc) { nsresult rv; - nsIPrincipal *principal = nullptr; - - rv = CheckFontLoad(aProxy, aFontFaceSrc, &principal); - NS_ENSURE_SUCCESS(rv, rv); nsIPresShell *ps = mPresContext->PresShell(); if (!ps) @@ -332,7 +328,7 @@ nsUserFontSet::StartLoad(gfxProxyFontEntry *aProxy, // get Content Security Policy from principal to pass into channel nsCOMPtr channelPolicy; nsCOMPtr csp; - rv = principal->GetCsp(getter_AddRefs(csp)); + rv = aProxy->mPrincipal->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp) { channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); @@ -382,8 +378,7 @@ nsUserFontSet::StartLoad(gfxProxyFontEntry *aProxy, rv = channel->AsyncOpen(streamLoader, nullptr); } else { nsRefPtr listener = - new nsCORSListenerProxy(streamLoader, principal, - false); + new nsCORSListenerProxy(streamLoader, aProxy->mPrincipal, false); rv = listener->Init(channel); if (NS_SUCCEEDED(rv)) { rv = channel->AsyncOpen(listener, nullptr); @@ -802,12 +797,9 @@ nsUserFontSet::LogMessage(gfxProxyFontEntry *aProxy, } nsresult -nsUserFontSet::CheckFontLoad(gfxProxyFontEntry *aFontToLoad, - const gfxFontFaceSrc *aFontFaceSrc, +nsUserFontSet::CheckFontLoad(const gfxFontFaceSrc *aFontFaceSrc, nsIPrincipal **aPrincipal) { - nsresult rv; - // check same-site origin nsIPresShell *ps = mPresContext->PresShell(); if (!ps) @@ -827,18 +819,11 @@ nsUserFontSet::CheckFontLoad(gfxProxyFontEntry *aFontToLoad, NS_ASSERTION(aFontFaceSrc->mOriginPrincipal, "null origin principal in @font-face rule"); if (aFontFaceSrc->mUseOriginPrincipal) { - nsCOMPtr p = do_QueryInterface(aFontFaceSrc->mOriginPrincipal); - *aPrincipal = p; + *aPrincipal = aFontFaceSrc->mOriginPrincipal; } - rv = nsFontFaceLoader::CheckLoadAllowed(*aPrincipal, aFontFaceSrc->mURI, - ps->GetDocument()); - if (NS_FAILED(rv)) { - LogMessage(aFontToLoad, "download not allowed", - nsIScriptError::errorFlag, rv); - } - - return rv; + return nsFontFaceLoader::CheckLoadAllowed(*aPrincipal, aFontFaceSrc->mURI, + ps->GetDocument()); } nsresult @@ -848,16 +833,12 @@ nsUserFontSet::SyncLoadFontData(gfxProxyFontEntry *aFontToLoad, uint32_t &aBufferLength) { nsresult rv; - nsIPrincipal *principal = nullptr; - - rv = CheckFontLoad(aFontToLoad, aFontFaceSrc, &principal); - NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr channel; // get Content Security Policy from principal to pass into channel nsCOMPtr channelPolicy; nsCOMPtr csp; - rv = principal->GetCsp(getter_AddRefs(csp)); + rv = aFontToLoad->mPrincipal->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp) { channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); diff --git a/layout/style/nsFontFaceLoader.h b/layout/style/nsFontFaceLoader.h index 9cd218d0d3bb..4c0e705400c3 100644 --- a/layout/style/nsFontFaceLoader.h +++ b/layout/style/nsFontFaceLoader.h @@ -74,9 +74,8 @@ protected: uint32_t aFlags = nsIScriptError::errorFlag, nsresult aStatus = NS_OK); - nsresult CheckFontLoad(gfxProxyFontEntry *aFontToLoad, - const gfxFontFaceSrc *aFontFaceSrc, - nsIPrincipal **aPrincipal); + virtual nsresult CheckFontLoad(const gfxFontFaceSrc *aFontFaceSrc, + nsIPrincipal **aPrincipal); virtual nsresult SyncLoadFontData(gfxProxyFontEntry *aFontToLoad, const gfxFontFaceSrc *aFontFaceSrc,