michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif /* MOZ_LOGGING */ michael@0: #include "prlog.h" michael@0: michael@0: #include "gfxUserFontSet.h" michael@0: #include "nsFont.h" michael@0: #include "gfxPlatform.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsICacheService.h" michael@0: #include "nsIProtocolHandler.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "gfxFontConstants.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "gfxPlatformFontList.h" michael@0: michael@0: #include "opentype-sanitiser.h" michael@0: #include "ots-memory-stream.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo * michael@0: gfxUserFontSet::GetUserFontsLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("userfonts"); michael@0: return sLog; michael@0: } michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: #define LOG(args) PR_LOG(GetUserFontsLog(), PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(GetUserFontsLog(), PR_LOG_DEBUG) michael@0: michael@0: static uint64_t sFontSetGeneration = 0; michael@0: michael@0: // TODO: support for unicode ranges not yet implemented michael@0: michael@0: gfxProxyFontEntry::gfxProxyFontEntry(const nsTArray& aFontFaceSrcList, michael@0: uint32_t aWeight, michael@0: int32_t aStretch, michael@0: uint32_t aItalicStyle, michael@0: const nsTArray& aFeatureSettings, michael@0: uint32_t aLanguageOverride, michael@0: gfxSparseBitSet *aUnicodeRanges) michael@0: : gfxFontEntry(NS_LITERAL_STRING("Proxy")), michael@0: mLoadingState(NOT_LOADING), michael@0: mUnsupportedFormat(false), michael@0: mLoader(nullptr) michael@0: { michael@0: mIsProxy = true; michael@0: mSrcList = aFontFaceSrcList; michael@0: mSrcIndex = 0; michael@0: mWeight = aWeight; michael@0: mStretch = aStretch; michael@0: // XXX Currently, we don't distinguish 'italic' and 'oblique' styles; michael@0: // we need to fix this. (Bug 543715) michael@0: mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; michael@0: mFeatureSettings.AppendElements(aFeatureSettings); michael@0: mLanguageOverride = aLanguageOverride; michael@0: mIsUserFont = true; michael@0: } michael@0: michael@0: gfxProxyFontEntry::~gfxProxyFontEntry() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: gfxProxyFontEntry::Matches(const nsTArray& aFontFaceSrcList, michael@0: uint32_t aWeight, michael@0: int32_t aStretch, michael@0: uint32_t aItalicStyle, michael@0: const nsTArray& aFeatureSettings, michael@0: uint32_t aLanguageOverride, michael@0: gfxSparseBitSet *aUnicodeRanges) michael@0: { michael@0: // XXX font entries don't distinguish italic from oblique (bug 543715) michael@0: bool isItalic = michael@0: (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; michael@0: michael@0: return mWeight == aWeight && michael@0: mStretch == aStretch && michael@0: mItalic == isItalic && michael@0: mFeatureSettings == aFeatureSettings && michael@0: mLanguageOverride == aLanguageOverride && michael@0: mSrcList == aFontFaceSrcList; michael@0: // XXX once we support unicode-range (bug 475891), michael@0: // we'll need to compare that here as well michael@0: } michael@0: michael@0: gfxFont* michael@0: gfxProxyFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) michael@0: { michael@0: // cannot create an actual font for a proxy entry michael@0: return nullptr; michael@0: } michael@0: michael@0: gfxUserFontSet::gfxUserFontSet() michael@0: : mFontFamilies(5), mLocalRulesUsed(false) michael@0: { michael@0: IncrementGeneration(); michael@0: gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList(); michael@0: if (fp) { michael@0: fp->AddUserFontSet(this); michael@0: } michael@0: } michael@0: michael@0: gfxUserFontSet::~gfxUserFontSet() michael@0: { michael@0: gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList(); michael@0: if (fp) { michael@0: fp->RemoveUserFontSet(this); michael@0: } michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxUserFontSet::AddFontFace(const nsAString& aFamilyName, michael@0: const nsTArray& aFontFaceSrcList, michael@0: uint32_t aWeight, michael@0: int32_t aStretch, michael@0: uint32_t aItalicStyle, michael@0: const nsTArray& aFeatureSettings, michael@0: const nsString& aLanguageOverride, michael@0: gfxSparseBitSet *aUnicodeRanges) michael@0: { michael@0: nsAutoString key(aFamilyName); michael@0: ToLowerCase(key); michael@0: michael@0: bool found; michael@0: michael@0: if (aWeight == 0) michael@0: aWeight = NS_FONT_WEIGHT_NORMAL; michael@0: michael@0: // stretch, italic/oblique ==> zero implies normal michael@0: michael@0: gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found); michael@0: if (!family) { michael@0: family = new gfxMixedFontFamily(aFamilyName); michael@0: mFontFamilies.Put(key, family); michael@0: } michael@0: michael@0: uint32_t languageOverride = michael@0: gfxFontStyle::ParseFontLanguageOverride(aLanguageOverride); michael@0: michael@0: // If there's already a proxy in the family whose descriptors all match, michael@0: // we can just move it to the end of the list instead of adding a new michael@0: // face that will always "shadow" the old one. michael@0: // Note that we can't do this for "real" (non-proxy) entries, even if the michael@0: // style descriptors match, as they might have had a different source list, michael@0: // but we no longer have the old source list available to check. michael@0: nsTArray >& fontList = family->GetFontList(); michael@0: for (uint32_t i = 0, count = fontList.Length(); i < count; i++) { michael@0: if (!fontList[i]->mIsProxy) { michael@0: continue; michael@0: } michael@0: michael@0: gfxProxyFontEntry *existingProxyEntry = michael@0: static_cast(fontList[i].get()); michael@0: if (!existingProxyEntry->Matches(aFontFaceSrcList, michael@0: aWeight, aStretch, aItalicStyle, michael@0: aFeatureSettings, languageOverride, michael@0: aUnicodeRanges)) { michael@0: continue; michael@0: } michael@0: michael@0: // We've found an entry that matches the new face exactly, so just add michael@0: // it to the end of the list. gfxMixedFontFamily::AddFontEntry() will michael@0: // automatically remove any earlier occurrence of the same proxy. michael@0: family->AddFontEntry(existingProxyEntry); michael@0: return existingProxyEntry; michael@0: } michael@0: michael@0: // construct a new face and add it into the family michael@0: gfxProxyFontEntry *proxyEntry = michael@0: new gfxProxyFontEntry(aFontFaceSrcList, aWeight, aStretch, michael@0: aItalicStyle, michael@0: aFeatureSettings, michael@0: languageOverride, michael@0: aUnicodeRanges); michael@0: family->AddFontEntry(proxyEntry); michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: LOG(("userfonts (%p) added (%s) with style: %s weight: %d stretch: %d", michael@0: this, NS_ConvertUTF16toUTF8(aFamilyName).get(), michael@0: (aItalicStyle & NS_FONT_STYLE_ITALIC ? "italic" : michael@0: (aItalicStyle & NS_FONT_STYLE_OBLIQUE ? "oblique" : "normal")), michael@0: aWeight, aStretch)); michael@0: } michael@0: #endif michael@0: michael@0: return proxyEntry; michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::AddFontFace(const nsAString& aFamilyName, michael@0: gfxFontEntry *aFontEntry) michael@0: { michael@0: nsAutoString key(aFamilyName); michael@0: ToLowerCase(key); michael@0: michael@0: bool found; michael@0: michael@0: gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found); michael@0: if (!family) { michael@0: family = new gfxMixedFontFamily(aFamilyName); michael@0: mFontFamilies.Put(key, family); michael@0: } michael@0: michael@0: family->AddFontEntry(aFontEntry); michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxUserFontSet::FindFontEntry(gfxFontFamily *aFamily, michael@0: const gfxFontStyle& aFontStyle, michael@0: bool& aNeedsBold, michael@0: bool& aWaitForUserFont) michael@0: { michael@0: aWaitForUserFont = false; michael@0: gfxMixedFontFamily *family = static_cast(aFamily); michael@0: michael@0: gfxFontEntry* fe = family->FindFontForStyle(aFontStyle, aNeedsBold); michael@0: michael@0: // if not a proxy, font has already been loaded michael@0: if (!fe->mIsProxy) { michael@0: return fe; michael@0: } michael@0: michael@0: gfxProxyFontEntry *proxyEntry = static_cast (fe); michael@0: michael@0: // if currently loading, return null for now michael@0: if (proxyEntry->mLoadingState > gfxProxyFontEntry::NOT_LOADING) { michael@0: aWaitForUserFont = michael@0: (proxyEntry->mLoadingState < gfxProxyFontEntry::LOADING_SLOWLY); michael@0: return nullptr; michael@0: } michael@0: michael@0: // hasn't been loaded yet, start the load process michael@0: LoadStatus status; michael@0: michael@0: // NOTE that if all sources in the entry fail, this will delete proxyEntry, michael@0: // so we cannot use it again if status==STATUS_END_OF_LIST michael@0: status = LoadNext(family, proxyEntry); michael@0: michael@0: // if the load succeeded immediately, the font entry was replaced so michael@0: // search again michael@0: if (status == STATUS_LOADED) { michael@0: return family->FindFontForStyle(aFontStyle, aNeedsBold); michael@0: } michael@0: michael@0: // check whether we should wait for load to complete before painting michael@0: // a fallback font -- but not if all sources failed (bug 633500) michael@0: aWaitForUserFont = (status != STATUS_END_OF_LIST) && michael@0: (proxyEntry->mLoadingState < gfxProxyFontEntry::LOADING_SLOWLY); michael@0: michael@0: // if either loading or an error occurred, return null michael@0: return nullptr; michael@0: } michael@0: michael@0: // Based on ots::ExpandingMemoryStream from ots-memory-stream.h, michael@0: // adapted to use Mozilla allocators and to allow the final michael@0: // memory buffer to be adopted by the client. michael@0: class ExpandingMemoryStream : public ots::OTSStream { michael@0: public: michael@0: ExpandingMemoryStream(size_t initial, size_t limit) michael@0: : mLength(initial), mLimit(limit), mOff(0) { michael@0: mPtr = NS_Alloc(mLength); michael@0: } michael@0: michael@0: ~ExpandingMemoryStream() { michael@0: NS_Free(mPtr); michael@0: } michael@0: michael@0: // return the buffer, and give up ownership of it michael@0: // so the caller becomes responsible to call NS_Free michael@0: // when finished with it michael@0: void* forget() { michael@0: void* p = mPtr; michael@0: mPtr = nullptr; michael@0: return p; michael@0: } michael@0: michael@0: bool WriteRaw(const void *data, size_t length) { michael@0: if ((mOff + length > mLength) || michael@0: (mLength > std::numeric_limits::max() - mOff)) { michael@0: if (mLength == mLimit) { michael@0: return false; michael@0: } michael@0: size_t newLength = (mLength + 1) * 2; michael@0: if (newLength < mLength) { michael@0: return false; michael@0: } michael@0: if (newLength > mLimit) { michael@0: newLength = mLimit; michael@0: } michael@0: mPtr = NS_Realloc(mPtr, newLength); michael@0: mLength = newLength; michael@0: return WriteRaw(data, length); michael@0: } michael@0: std::memcpy(static_cast(mPtr) + mOff, data, length); michael@0: mOff += length; michael@0: return true; michael@0: } michael@0: michael@0: bool Seek(off_t position) { michael@0: if (position < 0) { michael@0: return false; michael@0: } michael@0: if (static_cast(position) > mLength) { michael@0: return false; michael@0: } michael@0: mOff = position; michael@0: return true; michael@0: } michael@0: michael@0: off_t Tell() const { michael@0: return mOff; michael@0: } michael@0: michael@0: private: michael@0: void* mPtr; michael@0: size_t mLength; michael@0: const size_t mLimit; michael@0: off_t mOff; michael@0: }; michael@0: michael@0: static ots::TableAction michael@0: OTSTableAction(uint32_t aTag, void *aUserData) michael@0: { michael@0: // preserve Graphite and SVG tables michael@0: if (aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') || michael@0: aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') || michael@0: aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') || michael@0: aTag == TRUETYPE_TAG('G', 'l', 'a', 't') || michael@0: aTag == TRUETYPE_TAG('F', 'e', 'a', 't') || michael@0: aTag == TRUETYPE_TAG('S', 'V', 'G', ' ')) { michael@0: return ots::TABLE_ACTION_PASSTHRU; michael@0: } michael@0: return ots::TABLE_ACTION_DEFAULT; michael@0: } michael@0: michael@0: struct OTSCallbackUserData { michael@0: gfxUserFontSet *mFontSet; michael@0: gfxMixedFontFamily *mFamily; michael@0: gfxProxyFontEntry *mProxy; michael@0: }; michael@0: michael@0: /* static */ bool michael@0: gfxUserFontSet::OTSMessage(void *aUserData, const char *format, ...) michael@0: { michael@0: va_list va; michael@0: va_start(va, format); michael@0: michael@0: // buf should be more than adequate for any message OTS generates, michael@0: // so we don't worry about checking the result of vsnprintf() michael@0: char buf[512]; michael@0: (void)vsnprintf(buf, sizeof(buf), format, va); michael@0: michael@0: va_end(va); michael@0: michael@0: OTSCallbackUserData *d = static_cast(aUserData); michael@0: d->mFontSet->LogMessage(d->mFamily, d->mProxy, buf); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Call the OTS library to sanitize an sfnt before attempting to use it. michael@0: // Returns a newly-allocated block, or nullptr in case of fatal errors. michael@0: const uint8_t* michael@0: gfxUserFontSet::SanitizeOpenTypeData(gfxMixedFontFamily *aFamily, michael@0: gfxProxyFontEntry *aProxy, michael@0: const uint8_t* aData, uint32_t aLength, michael@0: uint32_t& aSaneLength, bool aIsCompressed) michael@0: { michael@0: // limit output/expansion to 256MB michael@0: ExpandingMemoryStream output(aIsCompressed ? aLength * 2 : aLength, michael@0: 1024 * 1024 * 256); michael@0: michael@0: OTSCallbackUserData userData; michael@0: userData.mFontSet = this; michael@0: userData.mFamily = aFamily; michael@0: userData.mProxy = aProxy; michael@0: michael@0: ots::SetTableActionCallback(&OTSTableAction, nullptr); michael@0: ots::SetMessageCallback(&gfxUserFontSet::OTSMessage, &userData); michael@0: michael@0: if (ots::Process(&output, aData, aLength)) { michael@0: aSaneLength = output.Tell(); michael@0: return static_cast(output.forget()); michael@0: } else { michael@0: aSaneLength = 0; michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: StoreUserFontData(gfxFontEntry* aFontEntry, gfxProxyFontEntry* aProxy, michael@0: bool aPrivate, const nsAString& aOriginalName, michael@0: FallibleTArray* aMetadata, uint32_t aMetaOrigLen) michael@0: { michael@0: if (!aFontEntry->mUserFontData) { michael@0: aFontEntry->mUserFontData = new gfxUserFontData; michael@0: } michael@0: gfxUserFontData* userFontData = aFontEntry->mUserFontData; michael@0: userFontData->mSrcIndex = aProxy->mSrcIndex; michael@0: const gfxFontFaceSrc& src = aProxy->mSrcList[aProxy->mSrcIndex]; michael@0: if (src.mIsLocal) { michael@0: userFontData->mLocalName = src.mLocalName; michael@0: } else { michael@0: userFontData->mURI = src.mURI; michael@0: userFontData->mPrincipal = aProxy->mPrincipal; michael@0: } michael@0: userFontData->mPrivate = aPrivate; michael@0: userFontData->mFormat = src.mFormatFlags; michael@0: userFontData->mRealName = aOriginalName; michael@0: if (aMetadata) { michael@0: userFontData->mMetadata.SwapElements(*aMetadata); michael@0: userFontData->mMetaOrigLen = aMetaOrigLen; michael@0: } michael@0: } michael@0: michael@0: struct WOFFHeader { michael@0: AutoSwap_PRUint32 signature; michael@0: AutoSwap_PRUint32 flavor; michael@0: AutoSwap_PRUint32 length; michael@0: AutoSwap_PRUint16 numTables; michael@0: AutoSwap_PRUint16 reserved; michael@0: AutoSwap_PRUint32 totalSfntSize; michael@0: AutoSwap_PRUint16 majorVersion; michael@0: AutoSwap_PRUint16 minorVersion; michael@0: AutoSwap_PRUint32 metaOffset; michael@0: AutoSwap_PRUint32 metaCompLen; michael@0: AutoSwap_PRUint32 metaOrigLen; michael@0: AutoSwap_PRUint32 privOffset; michael@0: AutoSwap_PRUint32 privLen; michael@0: }; michael@0: michael@0: void michael@0: gfxUserFontSet::CopyWOFFMetadata(const uint8_t* aFontData, michael@0: uint32_t aLength, michael@0: FallibleTArray* aMetadata, michael@0: uint32_t* aMetaOrigLen) michael@0: { michael@0: // This function may be called with arbitrary, unvalidated "font" data michael@0: // from @font-face, so it needs to be careful to bounds-check, etc., michael@0: // before trying to read anything. michael@0: // This just saves a copy of the compressed data block; it does NOT check michael@0: // that the block can be successfully decompressed, or that it contains michael@0: // well-formed/valid XML metadata. michael@0: if (aLength < sizeof(WOFFHeader)) { michael@0: return; michael@0: } michael@0: const WOFFHeader* woff = reinterpret_cast(aFontData); michael@0: uint32_t metaOffset = woff->metaOffset; michael@0: uint32_t metaCompLen = woff->metaCompLen; michael@0: if (!metaOffset || !metaCompLen || !woff->metaOrigLen) { michael@0: return; michael@0: } michael@0: if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) { michael@0: return; michael@0: } michael@0: if (!aMetadata->SetLength(woff->metaCompLen)) { michael@0: return; michael@0: } michael@0: memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen); michael@0: *aMetaOrigLen = woff->metaOrigLen; michael@0: } michael@0: michael@0: // This is called when a font download finishes. michael@0: // Ownership of aFontData passes in here, and the font set must michael@0: // ensure that it is eventually deleted via NS_Free(). michael@0: bool michael@0: gfxUserFontSet::OnLoadComplete(gfxMixedFontFamily *aFamily, michael@0: gfxProxyFontEntry *aProxy, michael@0: const uint8_t *aFontData, uint32_t aLength, michael@0: nsresult aDownloadStatus) michael@0: { michael@0: // forget about the loader, as we no longer potentially need to cancel it michael@0: // if the entry is obsoleted michael@0: aProxy->mLoader = nullptr; michael@0: michael@0: // download successful, make platform font using font data michael@0: if (NS_SUCCEEDED(aDownloadStatus)) { michael@0: gfxFontEntry *fe = LoadFont(aFamily, aProxy, aFontData, aLength); michael@0: aFontData = nullptr; michael@0: michael@0: if (fe) { michael@0: IncrementGeneration(); michael@0: return true; michael@0: } michael@0: michael@0: } else { michael@0: // download failed michael@0: LogMessage(aFamily, aProxy, michael@0: "download failed", nsIScriptError::errorFlag, michael@0: aDownloadStatus); michael@0: } michael@0: michael@0: if (aFontData) { michael@0: NS_Free((void*)aFontData); michael@0: } michael@0: michael@0: // error occurred, load next src michael@0: (void)LoadNext(aFamily, aProxy); michael@0: michael@0: // We ignore the status returned by LoadNext(); michael@0: // even if loading failed, we need to bump the font-set generation michael@0: // and return true in order to trigger reflow, so that fallback michael@0: // will be used where the text was "masked" by the pending download michael@0: IncrementGeneration(); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: gfxUserFontSet::LoadStatus michael@0: gfxUserFontSet::LoadNext(gfxMixedFontFamily *aFamily, michael@0: gfxProxyFontEntry *aProxyEntry) michael@0: { michael@0: uint32_t numSrc = aProxyEntry->mSrcList.Length(); michael@0: michael@0: NS_ASSERTION(aProxyEntry->mSrcIndex < numSrc, michael@0: "already at the end of the src list for user font"); michael@0: michael@0: if (aProxyEntry->mLoadingState == gfxProxyFontEntry::NOT_LOADING) { michael@0: aProxyEntry->mLoadingState = gfxProxyFontEntry::LOADING_STARTED; michael@0: aProxyEntry->mUnsupportedFormat = false; michael@0: } else { michael@0: // we were already loading; move to the next source, michael@0: // but don't reset state - if we've already timed out, michael@0: // that counts against the new download michael@0: aProxyEntry->mSrcIndex++; michael@0: } michael@0: michael@0: /* If there are any urls, prefer them to local */ michael@0: bool listHasURL = false; michael@0: for (uint32_t i = aProxyEntry->mSrcIndex; i < numSrc; i++) { michael@0: const gfxFontFaceSrc& currSrc = aProxyEntry->mSrcList[i]; michael@0: if (!currSrc.mIsLocal) { michael@0: listHasURL = true; michael@0: break; michael@0: } michael@0: } michael@0: nsPresContext *pres = GetPresContext(); michael@0: /* If we have no pres context, simply fail this load */ michael@0: if (!pres) listHasURL = true; michael@0: michael@0: // load each src entry in turn, until a local face is found michael@0: // or a download begins successfully michael@0: while (aProxyEntry->mSrcIndex < numSrc) { michael@0: const gfxFontFaceSrc& currSrc = aProxyEntry->mSrcList[aProxyEntry->mSrcIndex]; michael@0: michael@0: // src local ==> lookup and load immediately michael@0: michael@0: if (!listHasURL && currSrc.mIsLocal) { michael@0: nsFont font; michael@0: font.name = currSrc.mLocalName; michael@0: gfxFontEntry *fe = michael@0: gfxPlatform::GetPlatform()->LookupLocalFont(aProxyEntry, michael@0: currSrc.mLocalName); michael@0: pres->AddFontAttempt(font); michael@0: michael@0: /* No more fonts for you */ michael@0: if (pres->FontAttemptCountReached(font) || michael@0: pres->FontUseCountReached(font)) { michael@0: break; michael@0: } michael@0: michael@0: mLocalRulesUsed = true; michael@0: if (fe) { michael@0: pres->AddFontUse(font); michael@0: LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n", michael@0: this, aProxyEntry->mSrcIndex, michael@0: NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(), michael@0: NS_ConvertUTF16toUTF8(aFamily->Name()).get(), michael@0: uint32_t(mGeneration))); michael@0: fe->mFeatureSettings.AppendElements(aProxyEntry->mFeatureSettings); michael@0: fe->mLanguageOverride = aProxyEntry->mLanguageOverride; michael@0: // For src:local(), we don't care whether the request is from michael@0: // a private window as there's no issue of caching resources; michael@0: // local fonts are just available all the time. michael@0: StoreUserFontData(fe, aProxyEntry, false, nsString(), nullptr, 0); michael@0: ReplaceFontEntry(aFamily, aProxyEntry, fe); michael@0: return STATUS_LOADED; michael@0: } else { michael@0: LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", michael@0: this, aProxyEntry->mSrcIndex, michael@0: NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(), michael@0: NS_ConvertUTF16toUTF8(aFamily->Name()).get())); michael@0: } michael@0: } michael@0: michael@0: // src url ==> start the load process michael@0: else { michael@0: if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI, michael@0: currSrc.mFormatFlags)) { michael@0: michael@0: nsIPrincipal *principal = nullptr; michael@0: bool bypassCache; michael@0: nsresult rv = CheckFontLoad(&currSrc, &principal, &bypassCache); michael@0: michael@0: if (NS_SUCCEEDED(rv) && principal != nullptr) { michael@0: if (!bypassCache) { michael@0: // see if we have an existing entry for this source michael@0: gfxFontEntry *fe = michael@0: UserFontCache::GetFont(currSrc.mURI, principal, michael@0: aProxyEntry, michael@0: GetPrivateBrowsing()); michael@0: if (fe) { michael@0: ReplaceFontEntry(aFamily, aProxyEntry, fe); michael@0: return STATUS_LOADED; michael@0: } michael@0: } michael@0: michael@0: // record the principal returned by CheckFontLoad, michael@0: // for use when creating a channel michael@0: // and when caching the loaded entry michael@0: aProxyEntry->mPrincipal = principal; michael@0: michael@0: bool loadDoesntSpin = false; michael@0: rv = NS_URIChainHasFlags(currSrc.mURI, michael@0: nsIProtocolHandler::URI_SYNC_LOAD_IS_OK, michael@0: &loadDoesntSpin); michael@0: if (NS_SUCCEEDED(rv) && loadDoesntSpin) { michael@0: uint8_t *buffer = nullptr; michael@0: uint32_t bufferLength = 0; michael@0: michael@0: // sync load font immediately michael@0: rv = SyncLoadFontData(aProxyEntry, &currSrc, michael@0: buffer, bufferLength); michael@0: if (NS_SUCCEEDED(rv) && LoadFont(aFamily, aProxyEntry, michael@0: buffer, bufferLength)) { michael@0: return STATUS_LOADED; michael@0: } else { michael@0: LogMessage(aFamily, aProxyEntry, michael@0: "font load failed", michael@0: nsIScriptError::errorFlag, rv); michael@0: } michael@0: } else { michael@0: // otherwise load font async michael@0: rv = StartLoad(aFamily, aProxyEntry, &currSrc); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString fontURI; michael@0: currSrc.mURI->GetSpec(fontURI); michael@0: LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", michael@0: this, aProxyEntry->mSrcIndex, fontURI.get(), michael@0: NS_ConvertUTF16toUTF8(aFamily->Name()).get())); michael@0: } michael@0: #endif michael@0: return STATUS_LOADING; michael@0: } else { michael@0: LogMessage(aFamily, aProxyEntry, michael@0: "download failed", michael@0: nsIScriptError::errorFlag, rv); michael@0: } michael@0: } michael@0: } else { michael@0: LogMessage(aFamily, aProxyEntry, "download not allowed", michael@0: nsIScriptError::errorFlag, rv); michael@0: } michael@0: } else { michael@0: // We don't log a warning to the web console yet, michael@0: // as another source may load successfully michael@0: aProxyEntry->mUnsupportedFormat = true; michael@0: } michael@0: } michael@0: michael@0: aProxyEntry->mSrcIndex++; michael@0: } michael@0: michael@0: if (aProxyEntry->mUnsupportedFormat) { michael@0: LogMessage(aFamily, aProxyEntry, "no supported format found", michael@0: nsIScriptError::warningFlag); michael@0: } michael@0: michael@0: // all src's failed; mark this entry as unusable (so fallback will occur) michael@0: LOG(("userfonts (%p) failed all src for (%s)\n", michael@0: this, NS_ConvertUTF16toUTF8(aFamily->Name()).get())); michael@0: aProxyEntry->mLoadingState = gfxProxyFontEntry::LOADING_FAILED; michael@0: michael@0: return STATUS_END_OF_LIST; michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::IncrementGeneration() michael@0: { michael@0: // add one, increment again if zero michael@0: ++sFontSetGeneration; michael@0: if (sFontSetGeneration == 0) michael@0: ++sFontSetGeneration; michael@0: mGeneration = sFontSetGeneration; michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::RebuildLocalRules() michael@0: { michael@0: if (mLocalRulesUsed) { michael@0: DoRebuildUserFontSet(); michael@0: } michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxUserFontSet::LoadFont(gfxMixedFontFamily *aFamily, michael@0: gfxProxyFontEntry *aProxy, michael@0: const uint8_t *aFontData, uint32_t &aLength) michael@0: { michael@0: gfxFontEntry *fe = nullptr; michael@0: michael@0: gfxUserFontType fontType = michael@0: gfxFontUtils::DetermineFontDataType(aFontData, aLength); michael@0: michael@0: // Unwrap/decompress/sanitize or otherwise munge the downloaded data michael@0: // to make a usable sfnt structure. michael@0: michael@0: // Because platform font activation code may replace the name table michael@0: // in the font with a synthetic one, we save the original name so that michael@0: // it can be reported via the nsIDOMFontFace API. michael@0: nsAutoString originalFullName; michael@0: michael@0: // Call the OTS sanitizer; this will also decode WOFF to sfnt michael@0: // if necessary. The original data in aFontData is left unchanged. michael@0: uint32_t saneLen; michael@0: const uint8_t* saneData = michael@0: SanitizeOpenTypeData(aFamily, aProxy, aFontData, aLength, saneLen, michael@0: fontType == GFX_USERFONT_WOFF); michael@0: if (!saneData) { michael@0: LogMessage(aFamily, aProxy, "rejected by sanitizer"); michael@0: } michael@0: if (saneData) { michael@0: // The sanitizer ensures that we have a valid sfnt and a usable michael@0: // name table, so this should never fail unless we're out of michael@0: // memory, and GetFullNameFromSFNT is not directly exposed to michael@0: // arbitrary/malicious data from the web. michael@0: gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen, michael@0: originalFullName); michael@0: // Here ownership of saneData is passed to the platform, michael@0: // which will delete it when no longer required michael@0: fe = gfxPlatform::GetPlatform()->MakePlatformFont(aProxy, michael@0: saneData, michael@0: saneLen); michael@0: if (!fe) { michael@0: LogMessage(aFamily, aProxy, "not usable by platform"); michael@0: } michael@0: } michael@0: michael@0: if (fe) { michael@0: // Save a copy of the metadata block (if present) for nsIDOMFontFace michael@0: // to use if required. Ownership of the metadata block will be passed michael@0: // to the gfxUserFontData record below. michael@0: FallibleTArray metadata; michael@0: uint32_t metaOrigLen = 0; michael@0: if (fontType == GFX_USERFONT_WOFF) { michael@0: CopyWOFFMetadata(aFontData, aLength, &metadata, &metaOrigLen); michael@0: } michael@0: michael@0: // copy OpenType feature/language settings from the proxy to the michael@0: // newly-created font entry michael@0: fe->mFeatureSettings.AppendElements(aProxy->mFeatureSettings); michael@0: fe->mLanguageOverride = aProxy->mLanguageOverride; michael@0: StoreUserFontData(fe, aProxy, GetPrivateBrowsing(), michael@0: originalFullName, &metadata, metaOrigLen); michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString fontURI; michael@0: aProxy->mSrcList[aProxy->mSrcIndex].mURI->GetSpec(fontURI); michael@0: LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) gen: %8.8x\n", michael@0: this, aProxy->mSrcIndex, fontURI.get(), michael@0: NS_ConvertUTF16toUTF8(aFamily->Name()).get(), michael@0: uint32_t(mGeneration))); michael@0: } michael@0: #endif michael@0: ReplaceFontEntry(aFamily, aProxy, fe); michael@0: UserFontCache::CacheFont(fe); michael@0: } else { michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString fontURI; michael@0: aProxy->mSrcList[aProxy->mSrcIndex].mURI->GetSpec(fontURI); michael@0: LOG(("userfonts (%p) [src %d] failed uri: (%s) for (%s)" michael@0: " error making platform font\n", michael@0: this, aProxy->mSrcIndex, fontURI.get(), michael@0: NS_ConvertUTF16toUTF8(aFamily->Name()).get())); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // The downloaded data can now be discarded; the font entry is using the michael@0: // sanitized copy michael@0: NS_Free((void*)aFontData); michael@0: michael@0: return fe; michael@0: } michael@0: michael@0: gfxFontFamily* michael@0: gfxUserFontSet::GetFamily(const nsAString& aFamilyName) const michael@0: { michael@0: nsAutoString key(aFamilyName); michael@0: ToLowerCase(key); michael@0: michael@0: return mFontFamilies.GetWeak(key); michael@0: } michael@0: michael@0: struct FindFamilyCallbackData { michael@0: gfxFontEntry *mFontEntry; michael@0: gfxFontFamily *mFamily; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: FindFamilyCallback(const nsAString& aName, michael@0: gfxMixedFontFamily* aFamily, michael@0: void* aUserArg) michael@0: { michael@0: FindFamilyCallbackData *d = static_cast(aUserArg); michael@0: if (aFamily->ContainsFace(d->mFontEntry)) { michael@0: d->mFamily = aFamily; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: gfxFontFamily* michael@0: gfxUserFontSet::FindFamilyFor(gfxFontEntry* aFontEntry) const michael@0: { michael@0: FindFamilyCallbackData d = { aFontEntry, nullptr }; michael@0: mFontFamilies.EnumerateRead(FindFamilyCallback, &d); michael@0: return d.mFamily; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts michael@0: // across pages/fontsets rather than instantiating new platform fonts. michael@0: // michael@0: // Entries are added to this cache when a platform font is instantiated from michael@0: // downloaded data, and removed when the platform font entry is destroyed. michael@0: // We don't need to use a timed expiration scheme here because the gfxFontEntry michael@0: // for a downloaded font will be kept alive by its corresponding gfxFont michael@0: // instance(s) until they are deleted, and *that* happens using an expiration michael@0: // tracker (gfxFontCache). The result is that the downloaded font instances michael@0: // recorded here will persist between pages and can get reused (provided the michael@0: // source URI and principal match, of course). michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsTHashtable* michael@0: gfxUserFontSet::UserFontCache::sUserFonts = nullptr; michael@0: michael@0: NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver) michael@0: michael@0: PLDHashOperator michael@0: gfxUserFontSet::UserFontCache::Entry::RemoveIfPrivate(Entry* aEntry, michael@0: void* aUserData) michael@0: { michael@0: return aEntry->mPrivate ? PL_DHASH_REMOVE : PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: gfxUserFontSet::UserFontCache::Entry::RemoveIfMatches(Entry* aEntry, michael@0: void* aUserData) michael@0: { michael@0: return aEntry->GetFontEntry() == static_cast(aUserData) ? michael@0: PL_DHASH_REMOVE : PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: gfxUserFontSet::UserFontCache::Entry::DisconnectSVG(Entry* aEntry, michael@0: void* aUserData) michael@0: { michael@0: aEntry->GetFontEntry()->DisconnectSVG(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!sUserFonts) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID)) { michael@0: sUserFonts->Clear(); michael@0: } else if (!strcmp(aTopic, "last-pb-context-exited")) { michael@0: sUserFonts->EnumerateEntries(Entry::RemoveIfPrivate, nullptr); michael@0: } else if (!strcmp(aTopic, "xpcom-shutdown")) { michael@0: sUserFonts->EnumerateEntries(Entry::DisconnectSVG, nullptr); michael@0: } else { michael@0: NS_NOTREACHED("unexpected topic"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: gfxUserFontSet::UserFontCache::Entry::KeyEquals(const KeyTypePointer aKey) const michael@0: { michael@0: bool equal; michael@0: if (NS_FAILED(mURI->Equals(aKey->mURI, &equal)) || !equal) { michael@0: return false; michael@0: } michael@0: michael@0: if (NS_FAILED(mPrincipal->Equals(aKey->mPrincipal, &equal)) || !equal) { michael@0: return false; michael@0: } michael@0: michael@0: if (mPrivate != aKey->mPrivate) { michael@0: return false; michael@0: } michael@0: michael@0: const gfxFontEntry *fe = aKey->mFontEntry; michael@0: if (mFontEntry->mItalic != fe->mItalic || michael@0: mFontEntry->mWeight != fe->mWeight || michael@0: mFontEntry->mStretch != fe->mStretch || michael@0: mFontEntry->mFeatureSettings != fe->mFeatureSettings || michael@0: mFontEntry->mLanguageOverride != fe->mLanguageOverride || michael@0: mFontEntry->mFamilyName != fe->mFamilyName) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry *aFontEntry) michael@0: { michael@0: NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0, michael@0: "caching a font associated with no family yet"); michael@0: if (!sUserFonts) { michael@0: sUserFonts = new nsTHashtable; michael@0: michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: Flusher *flusher = new Flusher; michael@0: obs->AddObserver(flusher, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID, michael@0: false); michael@0: obs->AddObserver(flusher, "last-pb-context-exited", false); michael@0: obs->AddObserver(flusher, "xpcom-shutdown", false); michael@0: } michael@0: } michael@0: michael@0: gfxUserFontData *data = aFontEntry->mUserFontData; michael@0: sUserFonts->PutEntry(Key(data->mURI, data->mPrincipal, aFontEntry, michael@0: data->mPrivate)); michael@0: michael@0: #ifdef DEBUG_USERFONT_CACHE michael@0: printf("userfontcache added fontentry: %p\n", aFontEntry); michael@0: Dump(); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry *aFontEntry) michael@0: { michael@0: if (!sUserFonts) { michael@0: // if we've already deleted the cache (i.e. during shutdown), michael@0: // just ignore this michael@0: return; michael@0: } michael@0: michael@0: // We can't simply use RemoveEntry here because it's possible the principal michael@0: // may have changed since the font was cached, in which case the lookup michael@0: // would no longer find the entry (bug 838105). michael@0: sUserFonts->EnumerateEntries( michael@0: gfxUserFontSet::UserFontCache::Entry::RemoveIfMatches, aFontEntry); michael@0: michael@0: #ifdef DEBUG_USERFONT_CACHE michael@0: printf("userfontcache removed fontentry: %p\n", aFontEntry); michael@0: Dump(); michael@0: #endif michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxUserFontSet::UserFontCache::GetFont(nsIURI *aSrcURI, michael@0: nsIPrincipal *aPrincipal, michael@0: gfxProxyFontEntry *aProxy, michael@0: bool aPrivate) michael@0: { michael@0: if (!sUserFonts) { michael@0: return nullptr; michael@0: } michael@0: michael@0: Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, aPrincipal, aProxy, michael@0: aPrivate)); michael@0: if (entry) { michael@0: return entry->GetFontEntry(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::UserFontCache::Shutdown() michael@0: { michael@0: if (sUserFonts) { michael@0: delete sUserFonts; michael@0: sUserFonts = nullptr; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG_USERFONT_CACHE michael@0: michael@0: PLDHashOperator michael@0: gfxUserFontSet::UserFontCache::Entry::DumpEntry(Entry* aEntry, void* aUserData) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsAutoCString principalURISpec; michael@0: michael@0: nsCOMPtr principalURI; michael@0: rv = aEntry->mPrincipal->GetURI(getter_AddRefs(principalURI)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: principalURI->GetSpec(principalURISpec); michael@0: } michael@0: michael@0: bool setDomain = false; michael@0: nsCOMPtr domainURI; michael@0: michael@0: aEntry->mPrincipal->GetDomain(getter_AddRefs(domainURI)); michael@0: if (domainURI) { michael@0: setDomain = true; michael@0: } michael@0: michael@0: NS_ASSERTION(aEntry->mURI, "null URI in userfont cache entry"); michael@0: michael@0: printf("userfontcache fontEntry: %p fonturihash: %8.8x family: %s domainset: %s principal: [%s]\n", michael@0: aEntry->mFontEntry, michael@0: nsURIHashKey::HashKey(aEntry->mURI), michael@0: NS_ConvertUTF16toUTF8(aEntry->mFontEntry->FamilyName()).get(), michael@0: (setDomain ? "true" : "false"), michael@0: principalURISpec.get() michael@0: ); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: gfxUserFontSet::UserFontCache::Dump() michael@0: { michael@0: if (!sUserFonts) { michael@0: return; michael@0: } michael@0: michael@0: printf("userfontcache dump count: %d ========\n", sUserFonts->Count()); michael@0: sUserFonts->EnumerateEntries(Entry::DumpEntry, nullptr); michael@0: printf("userfontcache dump ==================\n"); michael@0: } michael@0: michael@0: #endif