diff -r 000000000000 -r 6474c204b198 gfx/thebes/gfxUserFontSet.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/thebes/gfxUserFontSet.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1056 @@ +/* -*- 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/. */ + +#ifdef MOZ_LOGGING +#define FORCE_PR_LOG /* Allow logging in the release build */ +#endif /* MOZ_LOGGING */ +#include "prlog.h" + +#include "gfxUserFontSet.h" +#include "nsFont.h" +#include "gfxPlatform.h" +#include "nsUnicharUtils.h" +#include "nsNetUtil.h" +#include "nsICacheService.h" +#include "nsIProtocolHandler.h" +#include "nsIPrincipal.h" +#include "gfxFontConstants.h" +#include "mozilla/Services.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatformFontList.h" + +#include "opentype-sanitiser.h" +#include "ots-memory-stream.h" + +using namespace mozilla; + +#ifdef PR_LOGGING +PRLogModuleInfo * +gfxUserFontSet::GetUserFontsLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("userfonts"); + return sLog; +} +#endif /* PR_LOGGING */ + +#define LOG(args) PR_LOG(GetUserFontsLog(), PR_LOG_DEBUG, args) +#define LOG_ENABLED() PR_LOG_TEST(GetUserFontsLog(), PR_LOG_DEBUG) + +static uint64_t sFontSetGeneration = 0; + +// TODO: support for unicode ranges not yet implemented + +gfxProxyFontEntry::gfxProxyFontEntry(const nsTArray& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint32_t aItalicStyle, + const nsTArray& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet *aUnicodeRanges) + : gfxFontEntry(NS_LITERAL_STRING("Proxy")), + mLoadingState(NOT_LOADING), + mUnsupportedFormat(false), + mLoader(nullptr) +{ + mIsProxy = true; + mSrcList = aFontFaceSrcList; + mSrcIndex = 0; + mWeight = aWeight; + mStretch = aStretch; + // XXX Currently, we don't distinguish 'italic' and 'oblique' styles; + // we need to fix this. (Bug 543715) + mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; + mFeatureSettings.AppendElements(aFeatureSettings); + mLanguageOverride = aLanguageOverride; + mIsUserFont = true; +} + +gfxProxyFontEntry::~gfxProxyFontEntry() +{ +} + +bool +gfxProxyFontEntry::Matches(const nsTArray& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint32_t aItalicStyle, + const nsTArray& aFeatureSettings, + uint32_t aLanguageOverride, + gfxSparseBitSet *aUnicodeRanges) +{ + // XXX font entries don't distinguish italic from oblique (bug 543715) + bool isItalic = + (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; + + return mWeight == aWeight && + mStretch == aStretch && + mItalic == isItalic && + mFeatureSettings == aFeatureSettings && + mLanguageOverride == aLanguageOverride && + mSrcList == aFontFaceSrcList; + // XXX once we support unicode-range (bug 475891), + // we'll need to compare that here as well +} + +gfxFont* +gfxProxyFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) +{ + // cannot create an actual font for a proxy entry + return nullptr; +} + +gfxUserFontSet::gfxUserFontSet() + : mFontFamilies(5), mLocalRulesUsed(false) +{ + IncrementGeneration(); + gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList(); + if (fp) { + fp->AddUserFontSet(this); + } +} + +gfxUserFontSet::~gfxUserFontSet() +{ + gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList(); + if (fp) { + fp->RemoveUserFontSet(this); + } +} + +gfxFontEntry* +gfxUserFontSet::AddFontFace(const nsAString& aFamilyName, + const nsTArray& aFontFaceSrcList, + uint32_t aWeight, + int32_t aStretch, + uint32_t aItalicStyle, + const nsTArray& aFeatureSettings, + const nsString& aLanguageOverride, + gfxSparseBitSet *aUnicodeRanges) +{ + nsAutoString key(aFamilyName); + ToLowerCase(key); + + bool found; + + if (aWeight == 0) + aWeight = NS_FONT_WEIGHT_NORMAL; + + // stretch, italic/oblique ==> zero implies normal + + gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found); + if (!family) { + family = new gfxMixedFontFamily(aFamilyName); + mFontFamilies.Put(key, family); + } + + uint32_t languageOverride = + gfxFontStyle::ParseFontLanguageOverride(aLanguageOverride); + + // If there's already a proxy 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 "real" (non-proxy) 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. + nsTArray >& fontList = family->GetFontList(); + for (uint32_t i = 0, count = fontList.Length(); i < count; i++) { + if (!fontList[i]->mIsProxy) { + continue; + } + + gfxProxyFontEntry *existingProxyEntry = + static_cast(fontList[i].get()); + if (!existingProxyEntry->Matches(aFontFaceSrcList, + aWeight, aStretch, aItalicStyle, + aFeatureSettings, languageOverride, + aUnicodeRanges)) { + continue; + } + + // We've found an entry that matches the new face exactly, so just add + // it to the end of the list. gfxMixedFontFamily::AddFontEntry() will + // automatically remove any earlier occurrence of the same proxy. + family->AddFontEntry(existingProxyEntry); + return existingProxyEntry; + } + + // construct a new face and add it into the family + gfxProxyFontEntry *proxyEntry = + new gfxProxyFontEntry(aFontFaceSrcList, aWeight, aStretch, + aItalicStyle, + aFeatureSettings, + languageOverride, + aUnicodeRanges); + family->AddFontEntry(proxyEntry); +#ifdef PR_LOGGING + if (LOG_ENABLED()) { + LOG(("userfonts (%p) added (%s) with style: %s weight: %d stretch: %d", + this, NS_ConvertUTF16toUTF8(aFamilyName).get(), + (aItalicStyle & NS_FONT_STYLE_ITALIC ? "italic" : + (aItalicStyle & NS_FONT_STYLE_OBLIQUE ? "oblique" : "normal")), + aWeight, aStretch)); + } +#endif + + return proxyEntry; +} + +void +gfxUserFontSet::AddFontFace(const nsAString& aFamilyName, + gfxFontEntry *aFontEntry) +{ + nsAutoString key(aFamilyName); + ToLowerCase(key); + + bool found; + + gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found); + if (!family) { + family = new gfxMixedFontFamily(aFamilyName); + mFontFamilies.Put(key, family); + } + + family->AddFontEntry(aFontEntry); +} + +gfxFontEntry* +gfxUserFontSet::FindFontEntry(gfxFontFamily *aFamily, + const gfxFontStyle& aFontStyle, + bool& aNeedsBold, + bool& aWaitForUserFont) +{ + aWaitForUserFont = false; + gfxMixedFontFamily *family = static_cast(aFamily); + + gfxFontEntry* fe = family->FindFontForStyle(aFontStyle, aNeedsBold); + + // if not a proxy, font has already been loaded + if (!fe->mIsProxy) { + return fe; + } + + gfxProxyFontEntry *proxyEntry = static_cast (fe); + + // if currently loading, return null for now + if (proxyEntry->mLoadingState > gfxProxyFontEntry::NOT_LOADING) { + aWaitForUserFont = + (proxyEntry->mLoadingState < gfxProxyFontEntry::LOADING_SLOWLY); + return nullptr; + } + + // hasn't been loaded yet, start the load process + LoadStatus status; + + // NOTE that if all sources in the entry fail, this will delete proxyEntry, + // so we cannot use it again if status==STATUS_END_OF_LIST + status = LoadNext(family, proxyEntry); + + // if the load succeeded immediately, the font entry was replaced so + // search again + if (status == STATUS_LOADED) { + return family->FindFontForStyle(aFontStyle, aNeedsBold); + } + + // check whether we should wait for load to complete before painting + // a fallback font -- but not if all sources failed (bug 633500) + aWaitForUserFont = (status != STATUS_END_OF_LIST) && + (proxyEntry->mLoadingState < gfxProxyFontEntry::LOADING_SLOWLY); + + // if either loading or an error occurred, return null + return nullptr; +} + +// 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 = NS_Alloc(mLength); + } + + ~ExpandingMemoryStream() { + NS_Free(mPtr); + } + + // return the buffer, and give up ownership of it + // so the caller becomes responsible to call NS_Free + // when finished with it + void* forget() { + void* p = mPtr; + mPtr = nullptr; + return p; + } + + bool WriteRaw(const void *data, size_t length) { + if ((mOff + length > mLength) || + (mLength > std::numeric_limits::max() - mOff)) { + if (mLength == mLimit) { + return false; + } + size_t newLength = (mLength + 1) * 2; + if (newLength < mLength) { + return false; + } + if (newLength > mLimit) { + newLength = mLimit; + } + mPtr = NS_Realloc(mPtr, newLength); + mLength = newLength; + return WriteRaw(data, length); + } + std::memcpy(static_cast(mPtr) + mOff, data, length); + mOff += length; + return true; + } + + bool Seek(off_t position) { + if (position < 0) { + return false; + } + if (static_cast(position) > mLength) { + return false; + } + mOff = position; + return true; + } + + off_t Tell() const { + return mOff; + } + +private: + void* mPtr; + size_t mLength; + const size_t mLimit; + off_t mOff; +}; + +static ots::TableAction +OTSTableAction(uint32_t aTag, void *aUserData) +{ + // preserve Graphite and SVG tables + if (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', ' ')) { + return ots::TABLE_ACTION_PASSTHRU; + } + return ots::TABLE_ACTION_DEFAULT; +} + +struct OTSCallbackUserData { + gfxUserFontSet *mFontSet; + gfxMixedFontFamily *mFamily; + gfxProxyFontEntry *mProxy; +}; + +/* static */ bool +gfxUserFontSet::OTSMessage(void *aUserData, const char *format, ...) +{ + va_list va; + va_start(va, format); + + // buf should be more than adequate for any message OTS generates, + // so we don't worry about checking the result of vsnprintf() + char buf[512]; + (void)vsnprintf(buf, sizeof(buf), format, va); + + va_end(va); + + OTSCallbackUserData *d = static_cast(aUserData); + d->mFontSet->LogMessage(d->mFamily, d->mProxy, buf); + + return false; +} + +// 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* +gfxUserFontSet::SanitizeOpenTypeData(gfxMixedFontFamily *aFamily, + gfxProxyFontEntry *aProxy, + const uint8_t* aData, uint32_t aLength, + uint32_t& aSaneLength, bool aIsCompressed) +{ + // limit output/expansion to 256MB + ExpandingMemoryStream output(aIsCompressed ? aLength * 2 : aLength, + 1024 * 1024 * 256); + + OTSCallbackUserData userData; + userData.mFontSet = this; + userData.mFamily = aFamily; + userData.mProxy = aProxy; + + ots::SetTableActionCallback(&OTSTableAction, nullptr); + ots::SetMessageCallback(&gfxUserFontSet::OTSMessage, &userData); + + if (ots::Process(&output, aData, aLength)) { + aSaneLength = output.Tell(); + return static_cast(output.forget()); + } else { + aSaneLength = 0; + return nullptr; + } +} + +static void +StoreUserFontData(gfxFontEntry* aFontEntry, gfxProxyFontEntry* aProxy, + bool aPrivate, const nsAString& aOriginalName, + FallibleTArray* aMetadata, uint32_t aMetaOrigLen) +{ + if (!aFontEntry->mUserFontData) { + aFontEntry->mUserFontData = new gfxUserFontData; + } + gfxUserFontData* userFontData = aFontEntry->mUserFontData; + userFontData->mSrcIndex = aProxy->mSrcIndex; + const gfxFontFaceSrc& src = aProxy->mSrcList[aProxy->mSrcIndex]; + if (src.mIsLocal) { + userFontData->mLocalName = src.mLocalName; + } else { + userFontData->mURI = src.mURI; + userFontData->mPrincipal = aProxy->mPrincipal; + } + userFontData->mPrivate = aPrivate; + userFontData->mFormat = src.mFormatFlags; + userFontData->mRealName = aOriginalName; + if (aMetadata) { + userFontData->mMetadata.SwapElements(*aMetadata); + userFontData->mMetaOrigLen = aMetaOrigLen; + } +} + +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; +}; + +void +gfxUserFontSet::CopyWOFFMetadata(const uint8_t* aFontData, + uint32_t aLength, + FallibleTArray* aMetadata, + uint32_t* aMetaOrigLen) +{ + // This function may be called with arbitrary, unvalidated "font" data + // from @font-face, so it needs to be careful to bounds-check, etc., + // before trying to read anything. + // This just saves a copy of the compressed data block; it does NOT check + // that the block can be successfully decompressed, or that it contains + // well-formed/valid XML metadata. + if (aLength < sizeof(WOFFHeader)) { + return; + } + const WOFFHeader* woff = reinterpret_cast(aFontData); + uint32_t metaOffset = woff->metaOffset; + uint32_t metaCompLen = woff->metaCompLen; + if (!metaOffset || !metaCompLen || !woff->metaOrigLen) { + return; + } + if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) { + return; + } + if (!aMetadata->SetLength(woff->metaCompLen)) { + return; + } + memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen); + *aMetaOrigLen = woff->metaOrigLen; +} + +// 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 NS_Free(). +bool +gfxUserFontSet::OnLoadComplete(gfxMixedFontFamily *aFamily, + gfxProxyFontEntry *aProxy, + 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 + aProxy->mLoader = nullptr; + + // download successful, make platform font using font data + if (NS_SUCCEEDED(aDownloadStatus)) { + gfxFontEntry *fe = LoadFont(aFamily, aProxy, aFontData, aLength); + aFontData = nullptr; + + if (fe) { + IncrementGeneration(); + return true; + } + + } else { + // download failed + LogMessage(aFamily, aProxy, + "download failed", nsIScriptError::errorFlag, + aDownloadStatus); + } + + if (aFontData) { + NS_Free((void*)aFontData); + } + + // error occurred, load next src + (void)LoadNext(aFamily, aProxy); + + // 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; +} + + +gfxUserFontSet::LoadStatus +gfxUserFontSet::LoadNext(gfxMixedFontFamily *aFamily, + gfxProxyFontEntry *aProxyEntry) +{ + uint32_t numSrc = aProxyEntry->mSrcList.Length(); + + NS_ASSERTION(aProxyEntry->mSrcIndex < numSrc, + "already at the end of the src list for user font"); + + if (aProxyEntry->mLoadingState == gfxProxyFontEntry::NOT_LOADING) { + aProxyEntry->mLoadingState = gfxProxyFontEntry::LOADING_STARTED; + aProxyEntry->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 + aProxyEntry->mSrcIndex++; + } + + /* If there are any urls, prefer them to local */ + bool listHasURL = false; + for (uint32_t i = aProxyEntry->mSrcIndex; i < numSrc; i++) { + const gfxFontFaceSrc& currSrc = aProxyEntry->mSrcList[i]; + if (!currSrc.mIsLocal) { + listHasURL = true; + break; + } + } + nsPresContext *pres = GetPresContext(); + /* If we have no pres context, simply fail this load */ + if (!pres) listHasURL = true; + + // load each src entry in turn, until a local face is found + // or a download begins successfully + while (aProxyEntry->mSrcIndex < numSrc) { + const gfxFontFaceSrc& currSrc = aProxyEntry->mSrcList[aProxyEntry->mSrcIndex]; + + // src local ==> lookup and load immediately + + if (!listHasURL && currSrc.mIsLocal) { + nsFont font; + font.name = currSrc.mLocalName; + gfxFontEntry *fe = + gfxPlatform::GetPlatform()->LookupLocalFont(aProxyEntry, + currSrc.mLocalName); + pres->AddFontAttempt(font); + + /* No more fonts for you */ + if (pres->FontAttemptCountReached(font) || + pres->FontUseCountReached(font)) { + break; + } + + mLocalRulesUsed = true; + if (fe) { + pres->AddFontUse(font); + LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n", + this, aProxyEntry->mSrcIndex, + NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(), + NS_ConvertUTF16toUTF8(aFamily->Name()).get(), + uint32_t(mGeneration))); + fe->mFeatureSettings.AppendElements(aProxyEntry->mFeatureSettings); + fe->mLanguageOverride = aProxyEntry->mLanguageOverride; + // 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, aProxyEntry, false, nsString(), nullptr, 0); + ReplaceFontEntry(aFamily, aProxyEntry, fe); + return STATUS_LOADED; + } else { + LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", + this, aProxyEntry->mSrcIndex, + NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(), + NS_ConvertUTF16toUTF8(aFamily->Name()).get())); + } + } + + // src url ==> start the load process + else { + if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI, + currSrc.mFormatFlags)) { + + nsIPrincipal *principal = nullptr; + bool bypassCache; + nsresult rv = CheckFontLoad(&currSrc, &principal, &bypassCache); + + if (NS_SUCCEEDED(rv) && principal != nullptr) { + if (!bypassCache) { + // see if we have an existing entry for this source + gfxFontEntry *fe = + UserFontCache::GetFont(currSrc.mURI, principal, + aProxyEntry, + GetPrivateBrowsing()); + if (fe) { + ReplaceFontEntry(aFamily, aProxyEntry, fe); + return STATUS_LOADED; + } + } + + // record the principal returned by CheckFontLoad, + // for use when creating a channel + // and when caching the loaded entry + aProxyEntry->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 = SyncLoadFontData(aProxyEntry, &currSrc, + buffer, bufferLength); + if (NS_SUCCEEDED(rv) && LoadFont(aFamily, aProxyEntry, + buffer, bufferLength)) { + return STATUS_LOADED; + } else { + LogMessage(aFamily, aProxyEntry, + "font load failed", + nsIScriptError::errorFlag, rv); + } + } else { + // otherwise load font async + rv = StartLoad(aFamily, 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(aFamily->Name()).get())); + } +#endif + return STATUS_LOADING; + } else { + LogMessage(aFamily, aProxyEntry, + "download failed", + nsIScriptError::errorFlag, rv); + } + } + } else { + LogMessage(aFamily, aProxyEntry, "download not allowed", + nsIScriptError::errorFlag, rv); + } + } else { + // We don't log a warning to the web console yet, + // as another source may load successfully + aProxyEntry->mUnsupportedFormat = true; + } + } + + aProxyEntry->mSrcIndex++; + } + + if (aProxyEntry->mUnsupportedFormat) { + LogMessage(aFamily, aProxyEntry, "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", + this, NS_ConvertUTF16toUTF8(aFamily->Name()).get())); + aProxyEntry->mLoadingState = gfxProxyFontEntry::LOADING_FAILED; + + return STATUS_END_OF_LIST; +} + +void +gfxUserFontSet::IncrementGeneration() +{ + // add one, increment again if zero + ++sFontSetGeneration; + if (sFontSetGeneration == 0) + ++sFontSetGeneration; + mGeneration = sFontSetGeneration; +} + +void +gfxUserFontSet::RebuildLocalRules() +{ + if (mLocalRulesUsed) { + DoRebuildUserFontSet(); + } +} + +gfxFontEntry* +gfxUserFontSet::LoadFont(gfxMixedFontFamily *aFamily, + gfxProxyFontEntry *aProxy, + const uint8_t *aFontData, uint32_t &aLength) +{ + gfxFontEntry *fe = nullptr; + + gfxUserFontType fontType = + gfxFontUtils::DetermineFontDataType(aFontData, aLength); + + // 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; + const uint8_t* saneData = + SanitizeOpenTypeData(aFamily, aProxy, aFontData, aLength, saneLen, + fontType == GFX_USERFONT_WOFF); + if (!saneData) { + LogMessage(aFamily, aProxy, "rejected by sanitizer"); + } + if (saneData) { + // 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); + // Here ownership of saneData is passed to the platform, + // which will delete it when no longer required + fe = gfxPlatform::GetPlatform()->MakePlatformFont(aProxy, + saneData, + saneLen); + if (!fe) { + LogMessage(aFamily, aProxy, "not usable by platform"); + } + } + + if (fe) { + // Save a copy of the metadata block (if present) for nsIDOMFontFace + // to use if required. Ownership of the metadata block will be passed + // to the gfxUserFontData record below. + FallibleTArray metadata; + uint32_t metaOrigLen = 0; + if (fontType == GFX_USERFONT_WOFF) { + CopyWOFFMetadata(aFontData, aLength, &metadata, &metaOrigLen); + } + + // copy OpenType feature/language settings from the proxy to the + // newly-created font entry + fe->mFeatureSettings.AppendElements(aProxy->mFeatureSettings); + fe->mLanguageOverride = aProxy->mLanguageOverride; + StoreUserFontData(fe, aProxy, GetPrivateBrowsing(), + originalFullName, &metadata, metaOrigLen); +#ifdef PR_LOGGING + if (LOG_ENABLED()) { + nsAutoCString fontURI; + aProxy->mSrcList[aProxy->mSrcIndex].mURI->GetSpec(fontURI); + LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) gen: %8.8x\n", + this, aProxy->mSrcIndex, fontURI.get(), + NS_ConvertUTF16toUTF8(aFamily->Name()).get(), + uint32_t(mGeneration))); + } +#endif + ReplaceFontEntry(aFamily, aProxy, fe); + UserFontCache::CacheFont(fe); + } else { +#ifdef PR_LOGGING + if (LOG_ENABLED()) { + nsAutoCString fontURI; + aProxy->mSrcList[aProxy->mSrcIndex].mURI->GetSpec(fontURI); + LOG(("userfonts (%p) [src %d] failed uri: (%s) for (%s)" + " error making platform font\n", + this, aProxy->mSrcIndex, fontURI.get(), + NS_ConvertUTF16toUTF8(aFamily->Name()).get())); + } +#endif + } + + // The downloaded data can now be discarded; the font entry is using the + // sanitized copy + NS_Free((void*)aFontData); + + return fe; +} + +gfxFontFamily* +gfxUserFontSet::GetFamily(const nsAString& aFamilyName) const +{ + nsAutoString key(aFamilyName); + ToLowerCase(key); + + return mFontFamilies.GetWeak(key); +} + +struct FindFamilyCallbackData { + gfxFontEntry *mFontEntry; + gfxFontFamily *mFamily; +}; + +static PLDHashOperator +FindFamilyCallback(const nsAString& aName, + gfxMixedFontFamily* aFamily, + void* aUserArg) +{ + FindFamilyCallbackData *d = static_cast(aUserArg); + if (aFamily->ContainsFace(d->mFontEntry)) { + d->mFamily = aFamily; + return PL_DHASH_STOP; + } + + return PL_DHASH_NEXT; +} + +gfxFontFamily* +gfxUserFontSet::FindFamilyFor(gfxFontEntry* aFontEntry) const +{ + FindFamilyCallbackData d = { aFontEntry, nullptr }; + mFontFamilies.EnumerateRead(FindFamilyCallback, &d); + return d.mFamily; +} + +/////////////////////////////////////////////////////////////////////////////// +// gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts +// across pages/fontsets rather than instantiating new platform fonts. +// +// Entries are added to this cache when a platform font is instantiated from +// downloaded data, and removed when the platform font entry is destroyed. +// We don't need to use a timed expiration scheme here because the gfxFontEntry +// for a downloaded font will be kept alive by its corresponding gfxFont +// instance(s) until they are deleted, and *that* happens using an expiration +// tracker (gfxFontCache). The result is that the downloaded font instances +// recorded here will persist between pages and can get reused (provided the +// source URI and principal match, of course). +/////////////////////////////////////////////////////////////////////////////// + +nsTHashtable* + gfxUserFontSet::UserFontCache::sUserFonts = nullptr; + +NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver) + +PLDHashOperator +gfxUserFontSet::UserFontCache::Entry::RemoveIfPrivate(Entry* aEntry, + void* aUserData) +{ + return aEntry->mPrivate ? PL_DHASH_REMOVE : PL_DHASH_NEXT; +} + +PLDHashOperator +gfxUserFontSet::UserFontCache::Entry::RemoveIfMatches(Entry* aEntry, + void* aUserData) +{ + return aEntry->GetFontEntry() == static_cast(aUserData) ? + PL_DHASH_REMOVE : PL_DHASH_NEXT; +} + +PLDHashOperator +gfxUserFontSet::UserFontCache::Entry::DisconnectSVG(Entry* aEntry, + void* aUserData) +{ + aEntry->GetFontEntry()->DisconnectSVG(); + return PL_DHASH_NEXT; +} + +NS_IMETHODIMP +gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!sUserFonts) { + return NS_OK; + } + + if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID)) { + sUserFonts->Clear(); + } else if (!strcmp(aTopic, "last-pb-context-exited")) { + sUserFonts->EnumerateEntries(Entry::RemoveIfPrivate, nullptr); + } else if (!strcmp(aTopic, "xpcom-shutdown")) { + sUserFonts->EnumerateEntries(Entry::DisconnectSVG, nullptr); + } else { + NS_NOTREACHED("unexpected topic"); + } + + return NS_OK; +} + +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; + } + + if (mPrivate != aKey->mPrivate) { + 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 || + 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 (!sUserFonts) { + sUserFonts = new nsTHashtable; + + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (obs) { + Flusher *flusher = new Flusher; + obs->AddObserver(flusher, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID, + false); + obs->AddObserver(flusher, "last-pb-context-exited", false); + obs->AddObserver(flusher, "xpcom-shutdown", false); + } + } + + gfxUserFontData *data = aFontEntry->mUserFontData; + sUserFonts->PutEntry(Key(data->mURI, data->mPrincipal, 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). + sUserFonts->EnumerateEntries( + gfxUserFontSet::UserFontCache::Entry::RemoveIfMatches, aFontEntry); + +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache removed fontentry: %p\n", aFontEntry); + Dump(); +#endif +} + +gfxFontEntry* +gfxUserFontSet::UserFontCache::GetFont(nsIURI *aSrcURI, + nsIPrincipal *aPrincipal, + gfxProxyFontEntry *aProxy, + bool aPrivate) +{ + if (!sUserFonts) { + return nullptr; + } + + Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, aPrincipal, aProxy, + aPrivate)); + if (entry) { + return entry->GetFontEntry(); + } + + return nullptr; +} + +void +gfxUserFontSet::UserFontCache::Shutdown() +{ + if (sUserFonts) { + delete sUserFonts; + sUserFonts = nullptr; + } +} + +#ifdef DEBUG_USERFONT_CACHE + +PLDHashOperator +gfxUserFontSet::UserFontCache::Entry::DumpEntry(Entry* aEntry, void* aUserData) +{ + nsresult rv; + + nsAutoCString principalURISpec; + + nsCOMPtr principalURI; + rv = aEntry->mPrincipal->GetURI(getter_AddRefs(principalURI)); + if (NS_SUCCEEDED(rv)) { + principalURI->GetSpec(principalURISpec); + } + + bool setDomain = false; + nsCOMPtr domainURI; + + aEntry->mPrincipal->GetDomain(getter_AddRefs(domainURI)); + if (domainURI) { + setDomain = true; + } + + NS_ASSERTION(aEntry->mURI, "null URI in userfont cache entry"); + + printf("userfontcache fontEntry: %p fonturihash: %8.8x family: %s domainset: %s principal: [%s]\n", + aEntry->mFontEntry, + nsURIHashKey::HashKey(aEntry->mURI), + NS_ConvertUTF16toUTF8(aEntry->mFontEntry->FamilyName()).get(), + (setDomain ? "true" : "false"), + principalURISpec.get() + ); + return PL_DHASH_NEXT; +} + +void +gfxUserFontSet::UserFontCache::Dump() +{ + if (!sUserFonts) { + return; + } + + printf("userfontcache dump count: %d ========\n", sUserFonts->Count()); + sUserFonts->EnumerateEntries(Entry::DumpEntry, nullptr); + printf("userfontcache dump ==================\n"); +} + +#endif