mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
0b7960f23a
MozReview-Commit-ID: J3MNO2un2ov --HG-- extra : rebase_source : 737943aea3d383669a7103d4e23f109889c09034
1563 lines
55 KiB
C++
1563 lines
55 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/Logging.h"
|
|
|
|
#include "gfxUserFontSet.h"
|
|
#include "gfxPlatform.h"
|
|
#include "gfxPrefs.h"
|
|
#include "nsIProtocolHandler.h"
|
|
#include "gfxFontConstants.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "gfxPlatformFontList.h"
|
|
#include "mozilla/ServoStyleSet.h"
|
|
#include "mozilla/PostTraversalTask.h"
|
|
|
|
#include "opentype-sanitiser.h"
|
|
#include "ots-memory-stream.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
mozilla::LogModule*
|
|
gfxUserFontSet::GetUserFontsLog()
|
|
{
|
|
static LazyLogModule sLog("userfonts");
|
|
return sLog;
|
|
}
|
|
|
|
#define LOG(args) MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
|
|
#define LOG_ENABLED() MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug)
|
|
|
|
static uint64_t sFontSetGeneration = 0;
|
|
|
|
// Based on ots::ExpandingMemoryStream from ots-memory-stream.h,
|
|
// adapted to use Mozilla allocators and to allow the final
|
|
// memory buffer to be adopted by the client.
|
|
class ExpandingMemoryStream : public ots::OTSStream {
|
|
public:
|
|
ExpandingMemoryStream(size_t initial, size_t limit)
|
|
: mLength(initial), mLimit(limit), mOff(0) {
|
|
mPtr = moz_xmalloc(mLength);
|
|
}
|
|
|
|
~ExpandingMemoryStream() {
|
|
free(mPtr);
|
|
}
|
|
|
|
// Return the buffer, resized to fit its contents (as it may have been
|
|
// over-allocated during growth), and give up ownership of it so the
|
|
// caller becomes responsible to call free() when finished with it.
|
|
void* forget() {
|
|
void* p = moz_xrealloc(mPtr, mOff);
|
|
mPtr = nullptr;
|
|
return p;
|
|
}
|
|
|
|
bool WriteRaw(const void* data, size_t length) {
|
|
if ((mOff + length > mLength) ||
|
|
(mLength > std::numeric_limits<size_t>::max() - mOff)) {
|
|
if (mLength == mLimit) {
|
|
return false;
|
|
}
|
|
size_t newLength = (mLength + 1) * 2;
|
|
if (newLength < mLength) {
|
|
return false;
|
|
}
|
|
if (newLength > mLimit) {
|
|
newLength = mLimit;
|
|
}
|
|
mPtr = moz_xrealloc(mPtr, newLength);
|
|
mLength = newLength;
|
|
return WriteRaw(data, length);
|
|
}
|
|
std::memcpy(static_cast<char*>(mPtr) + mOff, data, length);
|
|
mOff += length;
|
|
return true;
|
|
}
|
|
|
|
bool Seek(off_t position) {
|
|
if (position < 0) {
|
|
return false;
|
|
}
|
|
if (static_cast<size_t>(position) > mLength) {
|
|
return false;
|
|
}
|
|
mOff = position;
|
|
return true;
|
|
}
|
|
|
|
off_t Tell() const {
|
|
return mOff;
|
|
}
|
|
|
|
private:
|
|
void* mPtr;
|
|
size_t mLength;
|
|
const size_t mLimit;
|
|
off_t mOff;
|
|
};
|
|
|
|
gfxUserFontEntry::gfxUserFontEntry(gfxUserFontSet* aFontSet,
|
|
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
|
|
uint32_t aWeight,
|
|
int32_t aStretch,
|
|
uint8_t aStyle,
|
|
const nsTArray<gfxFontFeature>& aFeatureSettings,
|
|
uint32_t aLanguageOverride,
|
|
gfxCharacterMap* aUnicodeRanges,
|
|
uint8_t aFontDisplay)
|
|
: gfxFontEntry(NS_LITERAL_STRING("userfont")),
|
|
mUserFontLoadState(STATUS_NOT_LOADED),
|
|
mFontDataLoadingState(NOT_LOADING),
|
|
mUnsupportedFormat(false),
|
|
mFontDisplay(aFontDisplay),
|
|
mLoader(nullptr),
|
|
mFontSet(aFontSet)
|
|
{
|
|
MOZ_ASSERT(aWeight != 0,
|
|
"aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
|
|
mIsUserFontContainer = true;
|
|
mSrcList = aFontFaceSrcList;
|
|
mSrcIndex = 0;
|
|
mWeight = aWeight;
|
|
mStretch = aStretch;
|
|
mStyle = aStyle;
|
|
mFeatureSettings.AppendElements(aFeatureSettings);
|
|
mLanguageOverride = aLanguageOverride;
|
|
mCharacterMap = aUnicodeRanges;
|
|
}
|
|
|
|
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(!ServoStyleSet::IsInServoTraversal());
|
|
}
|
|
|
|
bool
|
|
gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
|
|
uint32_t aWeight,
|
|
int32_t aStretch,
|
|
uint8_t aStyle,
|
|
const nsTArray<gfxFontFeature>& aFeatureSettings,
|
|
uint32_t aLanguageOverride,
|
|
gfxCharacterMap* aUnicodeRanges,
|
|
uint8_t aFontDisplay)
|
|
{
|
|
return mWeight == aWeight &&
|
|
mStretch == aStretch &&
|
|
mStyle == aStyle &&
|
|
mFeatureSettings == aFeatureSettings &&
|
|
mLanguageOverride == aLanguageOverride &&
|
|
mSrcList == aFontFaceSrcList &&
|
|
mFontDisplay == aFontDisplay &&
|
|
((!aUnicodeRanges && !mCharacterMap) ||
|
|
(aUnicodeRanges && mCharacterMap && mCharacterMap->Equals(aUnicodeRanges)));
|
|
}
|
|
|
|
gfxFont*
|
|
gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, bool aNeedsBold)
|
|
{
|
|
NS_NOTREACHED("should only be creating a gfxFont"
|
|
" with an actual platform font entry");
|
|
|
|
// userfont entry is a container, can't create font from the container
|
|
return nullptr;
|
|
}
|
|
|
|
class MOZ_STACK_CLASS gfxOTSContext : public ots::OTSContext {
|
|
public:
|
|
explicit gfxOTSContext(gfxUserFontEntry* aUserFontEntry)
|
|
: mUserFontEntry(aUserFontEntry)
|
|
{
|
|
// Whether to apply OTS validation to OpenType Layout tables
|
|
mCheckOTLTables = gfxPrefs::ValidateOTLTables();
|
|
// Whether to preserve Variation tables in downloaded fonts
|
|
mKeepVariationTables = gfxPrefs::KeepVariationTables();
|
|
}
|
|
|
|
virtual ots::TableAction GetTableAction(uint32_t aTag) override {
|
|
// Preserve Graphite, color glyph and SVG tables,
|
|
// and possibly OTL and Variation tables (depending on prefs)
|
|
if ((!mCheckOTLTables &&
|
|
(aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') ||
|
|
aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') ||
|
|
aTag == TRUETYPE_TAG('G', 'S', 'U', 'B'))) ||
|
|
(mKeepVariationTables &&
|
|
(aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') ||
|
|
aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') ||
|
|
aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') ||
|
|
aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') ||
|
|
aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') ||
|
|
aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') ||
|
|
aTag == TRUETYPE_TAG('V', 'V', 'A', 'R'))) ||
|
|
aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
|
|
aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
|
|
aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
|
|
return ots::TABLE_ACTION_PASSTHRU;
|
|
}
|
|
return ots::TABLE_ACTION_DEFAULT;
|
|
}
|
|
|
|
virtual void Message(int level, const char* format,
|
|
...) MSGFUNC_FMT_ATTR override {
|
|
va_list va;
|
|
va_start(va, format);
|
|
|
|
nsCString msg;
|
|
msg.AppendPrintf(format, va);
|
|
|
|
va_end(va);
|
|
|
|
if (level > 0) {
|
|
// For warnings (rather than errors that cause the font to fail),
|
|
// we only report the first instance of any given message.
|
|
if (mWarningsIssued.Contains(msg)) {
|
|
return;
|
|
}
|
|
mWarningsIssued.PutEntry(msg);
|
|
}
|
|
|
|
mUserFontEntry->mFontSet->LogMessage(mUserFontEntry, msg.get());
|
|
}
|
|
|
|
private:
|
|
gfxUserFontEntry* mUserFontEntry;
|
|
nsTHashtable<nsCStringHashKey> mWarningsIssued;
|
|
bool mCheckOTLTables;
|
|
bool mKeepVariationTables;
|
|
};
|
|
|
|
// Call the OTS library to sanitize an sfnt before attempting to use it.
|
|
// Returns a newly-allocated block, or nullptr in case of fatal errors.
|
|
const uint8_t*
|
|
gfxUserFontEntry::SanitizeOpenTypeData(const uint8_t* aData,
|
|
uint32_t aLength,
|
|
uint32_t& aSaneLength,
|
|
gfxUserFontType aFontType)
|
|
{
|
|
if (aFontType == GFX_USERFONT_UNKNOWN) {
|
|
aSaneLength = 0;
|
|
return nullptr;
|
|
}
|
|
|
|
uint32_t lengthHint = aLength;
|
|
if (aFontType == GFX_USERFONT_WOFF) {
|
|
lengthHint *= 2;
|
|
} else if (aFontType == GFX_USERFONT_WOFF2) {
|
|
lengthHint *= 3;
|
|
}
|
|
|
|
// limit output/expansion to 256MB
|
|
ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256);
|
|
|
|
gfxOTSContext otsContext(this);
|
|
if (!otsContext.Process(&output, aData, aLength)) {
|
|
// Failed to decode/sanitize the font, so discard it.
|
|
aSaneLength = 0;
|
|
return nullptr;
|
|
}
|
|
|
|
aSaneLength = output.Tell();
|
|
return static_cast<const uint8_t*>(output.forget());
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
|
|
bool aPrivate,
|
|
const nsAString& aOriginalName,
|
|
FallibleTArray<uint8_t>* aMetadata,
|
|
uint32_t aMetaOrigLen,
|
|
uint8_t aCompression)
|
|
{
|
|
if (!aFontEntry->mUserFontData) {
|
|
aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>();
|
|
}
|
|
gfxUserFontData* userFontData = aFontEntry->mUserFontData.get();
|
|
userFontData->mSrcIndex = mSrcIndex;
|
|
const gfxFontFaceSrc& src = mSrcList[mSrcIndex];
|
|
switch (src.mSourceType) {
|
|
case gfxFontFaceSrc::eSourceType_Local:
|
|
userFontData->mLocalName = src.mLocalName;
|
|
break;
|
|
case gfxFontFaceSrc::eSourceType_URL:
|
|
userFontData->mURI = src.mURI;
|
|
userFontData->mPrincipal = mPrincipal;
|
|
break;
|
|
case gfxFontFaceSrc::eSourceType_Buffer:
|
|
userFontData->mIsBuffer = true;
|
|
break;
|
|
}
|
|
userFontData->mPrivate = aPrivate;
|
|
userFontData->mFormat = src.mFormatFlags;
|
|
userFontData->mRealName = aOriginalName;
|
|
if (aMetadata) {
|
|
userFontData->mMetadata.SwapElements(*aMetadata);
|
|
userFontData->mMetaOrigLen = aMetaOrigLen;
|
|
userFontData->mCompression = aCompression;
|
|
}
|
|
}
|
|
|
|
size_t
|
|
gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
return aMallocSizeOf(this)
|
|
+ mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf)
|
|
+ mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf)
|
|
+ mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
|
// Not counting mURI and mPrincipal, as those will be shared.
|
|
}
|
|
|
|
/*virtual*/
|
|
gfxUserFontFamily::~gfxUserFontFamily()
|
|
{
|
|
// Should not be dropped by stylo
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName,
|
|
nsACString& aURI)
|
|
{
|
|
aFamilyName.Assign(NS_ConvertUTF16toUTF8(mFamilyName));
|
|
|
|
aURI.Truncate();
|
|
if (mSrcIndex == mSrcList.Length()) {
|
|
aURI.AppendLiteral("(end of source list)");
|
|
} else {
|
|
if (mSrcList[mSrcIndex].mURI) {
|
|
mSrcList[mSrcIndex].mURI->GetSpec(aURI);
|
|
// 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(mSrcIndex < 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
|
|
mSrcIndex++;
|
|
}
|
|
|
|
DoLoadNextSrc(false);
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::ContinueLoad()
|
|
{
|
|
MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING);
|
|
MOZ_ASSERT(mSrcList[mSrcIndex].mSourceType == gfxFontFaceSrc::eSourceType_URL);
|
|
|
|
SetLoadState(STATUS_LOADING);
|
|
DoLoadNextSrc(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 nsIDocument::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 aForceAsync)
|
|
{
|
|
uint32_t numSrc = mSrcList.Length();
|
|
|
|
// load each src entry in turn, until a local face is found
|
|
// or a download begins successfully
|
|
while (mSrcIndex < numSrc) {
|
|
gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex];
|
|
|
|
// src local ==> lookup and load immediately
|
|
|
|
if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) {
|
|
// Don't look up local fonts if the font whitelist is being used.
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
gfxFontEntry* fe = pfl && pfl->IsFontFamilyWhitelistActive() ?
|
|
nullptr :
|
|
gfxPlatform::GetPlatform()->LookupLocalFont(currSrc.mLocalName,
|
|
mWeight,
|
|
mStretch,
|
|
mStyle);
|
|
nsTArray<gfxUserFontSet*> fontSets;
|
|
GetUserFontSets(fontSets);
|
|
for (gfxUserFontSet* fontSet : fontSets) {
|
|
// We need to note on each gfxUserFontSet that contains the user
|
|
// font entry that we used a local() rule.
|
|
fontSet->SetLocalRulesUsed();
|
|
}
|
|
if (fe) {
|
|
LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n",
|
|
mFontSet, mSrcIndex,
|
|
NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
|
|
NS_ConvertUTF16toUTF8(mFamilyName).get(),
|
|
uint32_t(mFontSet->mGeneration)));
|
|
fe->mFeatureSettings.AppendElements(mFeatureSettings);
|
|
fe->mLanguageOverride = mLanguageOverride;
|
|
fe->mFamilyName = mFamilyName;
|
|
// For src:local(), we don't care whether the request is from
|
|
// a private window as there's no issue of caching resources;
|
|
// local fonts are just available all the time.
|
|
StoreUserFontData(fe, false, nsString(), nullptr, 0,
|
|
gfxUserFontData::kUnknownCompression);
|
|
mPlatformFontEntry = fe;
|
|
SetLoadState(STATUS_LOADED);
|
|
Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
|
|
currSrc.mSourceType + 1);
|
|
return;
|
|
} else {
|
|
LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n",
|
|
mFontSet, mSrcIndex,
|
|
NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
|
|
NS_ConvertUTF16toUTF8(mFamilyName).get()));
|
|
}
|
|
}
|
|
|
|
// src url ==> start the load process
|
|
else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) {
|
|
if (gfxPlatform::GetPlatform()->IsFontFormatSupported(
|
|
currSrc.mFormatFlags)) {
|
|
|
|
if (ServoStyleSet* set = ServoStyleSet::Current()) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
gfxFontSrcPrincipal* principal = nullptr;
|
|
bool bypassCache;
|
|
nsresult rv = mFontSet->CheckFontLoad(&currSrc, &principal,
|
|
&bypassCache);
|
|
|
|
if (NS_SUCCEEDED(rv) && principal != nullptr) {
|
|
if (!bypassCache) {
|
|
// see if we have an existing entry for this source
|
|
gfxFontEntry* fe = gfxUserFontSet::
|
|
UserFontCache::GetFont(currSrc.mURI,
|
|
principal,
|
|
this,
|
|
mFontSet->GetPrivateBrowsing());
|
|
if (fe) {
|
|
mPlatformFontEntry = fe;
|
|
SetLoadState(STATUS_LOADED);
|
|
if (LOG_ENABLED()) {
|
|
LOG(("userfonts (%p) [src %d] "
|
|
"loaded uri from cache: (%s) for (%s)\n",
|
|
mFontSet, mSrcIndex,
|
|
currSrc.mURI->GetSpecOrDefault().get(),
|
|
NS_ConvertUTF16toUTF8(mFamilyName).get()));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ServoStyleSet* set = ServoStyleSet::Current()) {
|
|
// 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 returned by CheckFontLoad,
|
|
// for use when creating a channel
|
|
// and when caching the loaded entry
|
|
mPrincipal = principal;
|
|
|
|
bool loadDoesntSpin = false;
|
|
if (!aForceAsync) {
|
|
loadDoesntSpin = currSrc.mURI->SyncLoadIsOK();
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv) && loadDoesntSpin) {
|
|
uint8_t* buffer = nullptr;
|
|
uint32_t bufferLength = 0;
|
|
|
|
// sync load font immediately
|
|
rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer,
|
|
bufferLength);
|
|
|
|
if (NS_SUCCEEDED(rv) &&
|
|
LoadPlatformFont(buffer, bufferLength)) {
|
|
SetLoadState(STATUS_LOADED);
|
|
Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
|
|
currSrc.mSourceType + 1);
|
|
return;
|
|
} else {
|
|
mFontSet->LogMessage(this,
|
|
"font load failed",
|
|
nsIScriptError::errorFlag,
|
|
rv);
|
|
}
|
|
|
|
} else {
|
|
// otherwise load font async
|
|
rv = mFontSet->StartLoad(this, &currSrc);
|
|
bool loadOK = NS_SUCCEEDED(rv);
|
|
|
|
if (loadOK) {
|
|
if (LOG_ENABLED()) {
|
|
LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n",
|
|
mFontSet, mSrcIndex,
|
|
currSrc.mURI->GetSpecOrDefault().get(),
|
|
NS_ConvertUTF16toUTF8(mFamilyName).get()));
|
|
}
|
|
return;
|
|
} else {
|
|
mFontSet->LogMessage(this,
|
|
"download failed",
|
|
nsIScriptError::errorFlag,
|
|
rv);
|
|
}
|
|
}
|
|
} else {
|
|
mFontSet->LogMessage(this, "download not allowed",
|
|
nsIScriptError::errorFlag, rv);
|
|
}
|
|
} else {
|
|
// We don't log a warning to the web console yet,
|
|
// as another source may load successfully
|
|
mUnsupportedFormat = true;
|
|
}
|
|
}
|
|
|
|
// FontFace buffer ==> load immediately
|
|
|
|
else {
|
|
MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer);
|
|
|
|
uint8_t* buffer = nullptr;
|
|
uint32_t bufferLength = 0;
|
|
|
|
// sync load font immediately
|
|
currSrc.mBuffer->TakeBuffer(buffer, bufferLength);
|
|
if (buffer && LoadPlatformFont(buffer, bufferLength)) {
|
|
// LoadPlatformFont takes ownership of the buffer, so no need
|
|
// to free it here.
|
|
SetLoadState(STATUS_LOADED);
|
|
Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
|
|
currSrc.mSourceType + 1);
|
|
return;
|
|
} else {
|
|
mFontSet->LogMessage(this,
|
|
"font load failed",
|
|
nsIScriptError::errorFlag);
|
|
}
|
|
}
|
|
|
|
mSrcIndex++;
|
|
}
|
|
|
|
if (mUnsupportedFormat) {
|
|
mFontSet->LogMessage(this, "no supported format found",
|
|
nsIScriptError::warningFlag);
|
|
}
|
|
|
|
// all src's failed; mark this entry as unusable (so fallback will occur)
|
|
LOG(("userfonts (%p) failed all src for (%s)\n",
|
|
mFontSet, NS_ConvertUTF16toUTF8(mFamilyName).get()));
|
|
mFontDataLoadingState = LOADING_FAILED;
|
|
SetLoadState(STATUS_FAILED);
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState)
|
|
{
|
|
mUserFontLoadState = aLoadState;
|
|
}
|
|
|
|
MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)
|
|
|
|
bool
|
|
gfxUserFontEntry::LoadPlatformFont(const uint8_t* aFontData, uint32_t& aLength)
|
|
{
|
|
NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
|
|
mUserFontLoadState == STATUS_LOAD_PENDING ||
|
|
mUserFontLoadState == STATUS_LOADING) &&
|
|
mFontDataLoadingState < LOADING_FAILED,
|
|
"attempting to load a font that has either completed or failed");
|
|
|
|
gfxFontEntry* fe = nullptr;
|
|
|
|
gfxUserFontType fontType =
|
|
gfxFontUtils::DetermineFontDataType(aFontData, aLength);
|
|
Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(fontType));
|
|
|
|
// Unwrap/decompress/sanitize or otherwise munge the downloaded data
|
|
// to make a usable sfnt structure.
|
|
|
|
// Because platform font activation code may replace the name table
|
|
// in the font with a synthetic one, we save the original name so that
|
|
// it can be reported via the nsIDOMFontFace API.
|
|
nsAutoString originalFullName;
|
|
|
|
// Call the OTS sanitizer; this will also decode WOFF to sfnt
|
|
// if necessary. The original data in aFontData is left unchanged.
|
|
uint32_t saneLen;
|
|
uint32_t fontCompressionRatio = 0;
|
|
size_t computedSize = 0;
|
|
const uint8_t* saneData =
|
|
SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType);
|
|
if (!saneData) {
|
|
mFontSet->LogMessage(this, "rejected by sanitizer");
|
|
} else {
|
|
// Check whether saneData is a known OpenType format; it might be
|
|
// a TrueType Collection, which OTS would accept but we don't yet
|
|
// know how to handle. If so, discard.
|
|
if (gfxFontUtils::DetermineFontDataType(saneData, saneLen) !=
|
|
GFX_USERFONT_OPENTYPE) {
|
|
mFontSet->LogMessage(this, "not a supported OpenType format");
|
|
free((void*)saneData);
|
|
saneData = nullptr;
|
|
}
|
|
}
|
|
if (saneData) {
|
|
if (saneLen) {
|
|
fontCompressionRatio = uint32_t(100.0 * aLength / saneLen + 0.5);
|
|
if (fontType == GFX_USERFONT_WOFF ||
|
|
fontType == GFX_USERFONT_WOFF2) {
|
|
Telemetry::Accumulate(fontType == GFX_USERFONT_WOFF ?
|
|
Telemetry::WEBFONT_COMPRESSION_WOFF :
|
|
Telemetry::WEBFONT_COMPRESSION_WOFF2,
|
|
fontCompressionRatio);
|
|
}
|
|
}
|
|
|
|
// The sanitizer ensures that we have a valid sfnt and a usable
|
|
// name table, so this should never fail unless we're out of
|
|
// memory, and GetFullNameFromSFNT is not directly exposed to
|
|
// arbitrary/malicious data from the web.
|
|
gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen,
|
|
originalFullName);
|
|
|
|
// Record size for memory reporting purposes. We measure this now
|
|
// because by the time we potentially want to collect reports, this
|
|
// data block may have been handed off to opaque OS font APIs that
|
|
// don't allow us to retrieve or measure it directly.
|
|
// The *OnAlloc function will also tell DMD about this block, as the
|
|
// OS font code may hold on to it for an extended period.
|
|
computedSize = UserFontMallocSizeOfOnAlloc(saneData);
|
|
|
|
// Here ownership of saneData is passed to the platform,
|
|
// which will delete it when no longer required
|
|
fe = gfxPlatform::GetPlatform()->MakePlatformFont(mName,
|
|
mWeight,
|
|
mStretch,
|
|
mStyle,
|
|
saneData,
|
|
saneLen);
|
|
if (!fe) {
|
|
mFontSet->LogMessage(this, "not usable by platform");
|
|
}
|
|
}
|
|
|
|
if (fe) {
|
|
fe->mComputedSizeOfUserFont = computedSize;
|
|
|
|
// Save a copy of the metadata block (if present) for nsIDOMFontFace
|
|
// to use if required. Ownership of the metadata block will be passed
|
|
// to the gfxUserFontData record below.
|
|
FallibleTArray<uint8_t> metadata;
|
|
uint32_t metaOrigLen = 0;
|
|
uint8_t compression = gfxUserFontData::kUnknownCompression;
|
|
if (fontType == GFX_USERFONT_WOFF) {
|
|
CopyWOFFMetadata<WOFFHeader>(aFontData, aLength,
|
|
&metadata, &metaOrigLen);
|
|
compression = gfxUserFontData::kZlibCompression;
|
|
} else if (fontType == GFX_USERFONT_WOFF2) {
|
|
CopyWOFFMetadata<WOFF2Header>(aFontData, aLength,
|
|
&metadata, &metaOrigLen);
|
|
compression = gfxUserFontData::kBrotliCompression;
|
|
}
|
|
|
|
// copy OpenType feature/language settings from the userfont entry to the
|
|
// newly-created font entry
|
|
fe->mFeatureSettings.AppendElements(mFeatureSettings);
|
|
fe->mLanguageOverride = mLanguageOverride;
|
|
fe->mFamilyName = mFamilyName;
|
|
StoreUserFontData(fe, mFontSet->GetPrivateBrowsing(), originalFullName,
|
|
&metadata, metaOrigLen, compression);
|
|
if (LOG_ENABLED()) {
|
|
LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) "
|
|
"(%p) gen: %8.8x compress: %d%%\n",
|
|
mFontSet, mSrcIndex,
|
|
mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(),
|
|
NS_ConvertUTF16toUTF8(mFamilyName).get(),
|
|
this, uint32_t(mFontSet->mGeneration), fontCompressionRatio));
|
|
}
|
|
mPlatformFontEntry = fe;
|
|
SetLoadState(STATUS_LOADED);
|
|
gfxUserFontSet::UserFontCache::CacheFont(fe);
|
|
} else {
|
|
if (LOG_ENABLED()) {
|
|
LOG(("userfonts (%p) [src %d] failed uri: (%s) for (%s)"
|
|
" error making platform font\n",
|
|
mFontSet, mSrcIndex,
|
|
mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(),
|
|
NS_ConvertUTF16toUTF8(mFamilyName).get()));
|
|
}
|
|
}
|
|
|
|
// The downloaded data can now be discarded; the font entry is using the
|
|
// sanitized copy
|
|
free((void*)aFontData);
|
|
|
|
return fe != nullptr;
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::Load()
|
|
{
|
|
if (mUserFontLoadState == STATUS_NOT_LOADED) {
|
|
LoadNextSrc();
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::IncrementGeneration()
|
|
{
|
|
nsTArray<gfxUserFontSet*> fontSets;
|
|
GetUserFontSets(fontSets);
|
|
for (gfxUserFontSet* fontSet : fontSets) {
|
|
fontSet->IncrementGeneration();
|
|
}
|
|
}
|
|
|
|
// This is called when a font download finishes.
|
|
// Ownership of aFontData passes in here, and the font set must
|
|
// ensure that it is eventually deleted via free().
|
|
bool
|
|
gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData,
|
|
uint32_t aLength,
|
|
nsresult aDownloadStatus)
|
|
{
|
|
// forget about the loader, as we no longer potentially need to cancel it
|
|
// if the entry is obsoleted
|
|
mLoader = nullptr;
|
|
|
|
// download successful, make platform font using font data
|
|
if (NS_SUCCEEDED(aDownloadStatus) &&
|
|
mFontDataLoadingState != LOADING_TIMED_OUT) {
|
|
bool loaded = LoadPlatformFont(aFontData, aLength);
|
|
aFontData = nullptr;
|
|
|
|
if (loaded) {
|
|
IncrementGeneration();
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
// download failed
|
|
mFontSet->LogMessage(this,
|
|
(mFontDataLoadingState != LOADING_TIMED_OUT ?
|
|
"download failed" : "download timed out"),
|
|
nsIScriptError::errorFlag,
|
|
aDownloadStatus);
|
|
}
|
|
|
|
if (aFontData) {
|
|
free((void*)aFontData);
|
|
}
|
|
|
|
// error occurred, load next src if load not yet timed out
|
|
if (mFontDataLoadingState != LOADING_TIMED_OUT) {
|
|
LoadNextSrc();
|
|
}
|
|
|
|
// We ignore the status returned by LoadNext();
|
|
// even if loading failed, we need to bump the font-set generation
|
|
// and return true in order to trigger reflow, so that fallback
|
|
// will be used where the text was "masked" by the pending download
|
|
IncrementGeneration();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
gfxUserFontEntry::GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult)
|
|
{
|
|
aResult.Clear();
|
|
aResult.AppendElement(mFontSet);
|
|
}
|
|
|
|
gfxUserFontSet::gfxUserFontSet()
|
|
: mFontFamilies(4),
|
|
mLocalRulesUsed(false),
|
|
mRebuildLocalRules(false),
|
|
mDownloadCount(0),
|
|
mDownloadSize(0)
|
|
{
|
|
IncrementGeneration(true);
|
|
gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
|
|
if (fp) {
|
|
fp->AddUserFontSet(this);
|
|
}
|
|
}
|
|
|
|
gfxUserFontSet::~gfxUserFontSet()
|
|
{
|
|
gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
|
|
if (fp) {
|
|
fp->RemoveUserFontSet(this);
|
|
}
|
|
|
|
UserFontCache::ClearAllowedFontSets(this);
|
|
}
|
|
|
|
already_AddRefed<gfxUserFontEntry>
|
|
gfxUserFontSet::FindOrCreateUserFontEntry(
|
|
const nsAString& aFamilyName,
|
|
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
|
|
uint32_t aWeight,
|
|
int32_t aStretch,
|
|
uint8_t aStyle,
|
|
const nsTArray<gfxFontFeature>& aFeatureSettings,
|
|
uint32_t aLanguageOverride,
|
|
gfxCharacterMap* aUnicodeRanges,
|
|
uint8_t aFontDisplay)
|
|
{
|
|
RefPtr<gfxUserFontEntry> entry;
|
|
|
|
// If there's already a userfont entry in the family whose descriptors all match,
|
|
// we can just move it to the end of the list instead of adding a new
|
|
// face that will always "shadow" the old one.
|
|
// Note that we can't do this for platform font entries, even if the
|
|
// style descriptors match, as they might have had a different source list,
|
|
// but we no longer have the old source list available to check.
|
|
gfxUserFontFamily* family = LookupFamily(aFamilyName);
|
|
if (family) {
|
|
entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aWeight,
|
|
aStretch, aStyle,
|
|
aFeatureSettings, aLanguageOverride,
|
|
aUnicodeRanges, aFontDisplay);
|
|
}
|
|
|
|
if (!entry) {
|
|
entry = CreateUserFontEntry(aFontFaceSrcList, aWeight, aStretch,
|
|
aStyle, aFeatureSettings,
|
|
aLanguageOverride, aUnicodeRanges,
|
|
aFontDisplay);
|
|
entry->mFamilyName = aFamilyName;
|
|
}
|
|
|
|
return entry.forget();
|
|
}
|
|
|
|
already_AddRefed<gfxUserFontEntry>
|
|
gfxUserFontSet::CreateUserFontEntry(
|
|
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
|
|
uint32_t aWeight,
|
|
int32_t aStretch,
|
|
uint8_t aStyle,
|
|
const nsTArray<gfxFontFeature>& aFeatureSettings,
|
|
uint32_t aLanguageOverride,
|
|
gfxCharacterMap* aUnicodeRanges,
|
|
uint8_t aFontDisplay)
|
|
{
|
|
|
|
RefPtr<gfxUserFontEntry> userFontEntry =
|
|
new gfxUserFontEntry(this, aFontFaceSrcList, aWeight,
|
|
aStretch, aStyle, aFeatureSettings,
|
|
aLanguageOverride, aUnicodeRanges, aFontDisplay);
|
|
return userFontEntry.forget();
|
|
}
|
|
|
|
gfxUserFontEntry*
|
|
gfxUserFontSet::FindExistingUserFontEntry(
|
|
gfxUserFontFamily* aFamily,
|
|
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
|
|
uint32_t aWeight,
|
|
int32_t aStretch,
|
|
uint8_t aStyle,
|
|
const nsTArray<gfxFontFeature>& aFeatureSettings,
|
|
uint32_t aLanguageOverride,
|
|
gfxCharacterMap* aUnicodeRanges,
|
|
uint8_t aFontDisplay)
|
|
{
|
|
MOZ_ASSERT(aWeight != 0,
|
|
"aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
|
|
|
|
nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
|
|
|
|
for (size_t i = 0, count = fontList.Length(); i < count; i++) {
|
|
if (!fontList[i]->mIsUserFontContainer) {
|
|
continue;
|
|
}
|
|
|
|
gfxUserFontEntry* existingUserFontEntry =
|
|
static_cast<gfxUserFontEntry*>(fontList[i].get());
|
|
if (!existingUserFontEntry->Matches(aFontFaceSrcList,
|
|
aWeight, aStretch, aStyle,
|
|
aFeatureSettings, aLanguageOverride,
|
|
aUnicodeRanges, aFontDisplay)) {
|
|
continue;
|
|
}
|
|
|
|
return existingUserFontEntry;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::AddUserFontEntry(const nsAString& aFamilyName,
|
|
gfxUserFontEntry* aUserFontEntry)
|
|
{
|
|
gfxUserFontFamily* family = GetFamily(aFamilyName);
|
|
family->AddFontEntry(aUserFontEntry);
|
|
|
|
if (LOG_ENABLED()) {
|
|
LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %d "
|
|
"stretch: %d display: %d",
|
|
this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aUserFontEntry,
|
|
(aUserFontEntry->IsItalic() ? "italic" :
|
|
(aUserFontEntry->IsOblique() ? "oblique" : "normal")),
|
|
aUserFontEntry->Weight(), aUserFontEntry->Stretch(),
|
|
aUserFontEntry->GetFontDisplay()));
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::IncrementGeneration(bool aIsRebuild)
|
|
{
|
|
// add one, increment again if zero
|
|
++sFontSetGeneration;
|
|
if (sFontSetGeneration == 0)
|
|
++sFontSetGeneration;
|
|
mGeneration = sFontSetGeneration;
|
|
if (aIsRebuild) {
|
|
mRebuildGeneration = mGeneration;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::RebuildLocalRules()
|
|
{
|
|
if (mLocalRulesUsed) {
|
|
mRebuildLocalRules = true;
|
|
DoRebuildUserFontSet();
|
|
}
|
|
}
|
|
|
|
gfxUserFontFamily*
|
|
gfxUserFontSet::LookupFamily(const nsAString& aFamilyName) const
|
|
{
|
|
nsAutoString key(aFamilyName);
|
|
ToLowerCase(key);
|
|
|
|
return mFontFamilies.GetWeak(key);
|
|
}
|
|
|
|
bool
|
|
gfxUserFontSet::ContainsUserFontSetFonts(const FontFamilyList& aFontList) const
|
|
{
|
|
for (const FontFamilyName& name : aFontList.GetFontlist()->mNames) {
|
|
if (name.mType != eFamily_named &&
|
|
name.mType != eFamily_named_quoted) {
|
|
continue;
|
|
}
|
|
if (LookupFamily(name.mName)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gfxUserFontFamily*
|
|
gfxUserFontSet::GetFamily(const nsAString& aFamilyName)
|
|
{
|
|
nsAutoString key(aFamilyName);
|
|
ToLowerCase(key);
|
|
|
|
gfxUserFontFamily* family = mFontFamilies.GetWeak(key);
|
|
if (!family) {
|
|
family = new gfxUserFontFamily(aFamilyName);
|
|
mFontFamilies.Put(key, family);
|
|
}
|
|
return family;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts
|
|
// across pages/fontsets rather than instantiating new platform fonts.
|
|
//
|
|
// Entries are added to this cache when a platform font is instantiated from
|
|
// downloaded data, and removed when the platform font entry is destroyed.
|
|
// We don't need to use a timed expiration scheme here because the gfxFontEntry
|
|
// for a downloaded font will be kept alive by its corresponding gfxFont
|
|
// instance(s) until they are deleted, and *that* happens using an expiration
|
|
// tracker (gfxFontCache). The result is that the downloaded font instances
|
|
// recorded here will persist between pages and can get reused (provided the
|
|
// source URI and principal match, of course).
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
nsTHashtable<gfxUserFontSet::UserFontCache::Entry>*
|
|
gfxUserFontSet::UserFontCache::sUserFonts = nullptr;
|
|
|
|
uint32_t
|
|
gfxUserFontSet::UserFontCache::sGeneration = 0;
|
|
|
|
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 {
|
|
NS_NOTREACHED("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->mStyle != fe->mStyle ||
|
|
mFontEntry->mWeight != fe->mWeight ||
|
|
mFontEntry->mStretch != fe->mStretch ||
|
|
mFontEntry->mFeatureSettings != fe->mFeatureSettings ||
|
|
mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
|
|
mFontEntry->mFamilyName != fe->mFamilyName) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry)
|
|
{
|
|
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));
|
|
|
|
++sGeneration;
|
|
|
|
#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(gfxFontSrcURI* aSrcURI,
|
|
gfxFontSrcPrincipal* aPrincipal,
|
|
gfxUserFontEntry* aUserFontEntry,
|
|
bool aPrivate)
|
|
{
|
|
if (!sUserFonts ||
|
|
Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Ignore principal when looking up a data: URI.
|
|
gfxFontSrcPrincipal* principal;
|
|
if (IgnorePrincipal(aSrcURI)) {
|
|
principal = nullptr;
|
|
} else {
|
|
principal = aPrincipal;
|
|
}
|
|
|
|
Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, principal, aUserFontEntry,
|
|
aPrivate));
|
|
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.
|
|
bool allowed = false;
|
|
if (ServoStyleSet::IsInServoTraversal()) {
|
|
// Use the cached IsFontLoadAllowed results in mAllowedFontSets.
|
|
allowed = entry->CheckIsFontSetAllowedAndDispatchViolations(
|
|
aUserFontEntry->mFontSet);
|
|
} else {
|
|
// Call IsFontLoadAllowed directly, since we are on the main thread.
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
nsIPrincipal* principal = aPrincipal ? aPrincipal->get() : nullptr;
|
|
allowed = aUserFontEntry->mFontSet->IsFontLoadAllowed(
|
|
aSrcURI->get(),
|
|
principal,
|
|
/* aViolations */ nullptr);
|
|
MOZ_ASSERT(!entry->IsFontSetAllowedKnown(aUserFontEntry->mFontSet) ||
|
|
entry->CheckIsFontSetAllowed(aUserFontEntry->mFontSet) == allowed,
|
|
"why does IsFontLoadAllowed return a different value from "
|
|
"the cached value in mAllowedFontSets?");
|
|
}
|
|
|
|
if (!allowed) {
|
|
return nullptr;
|
|
}
|
|
|
|
return entry->GetFontEntry();
|
|
}
|
|
|
|
/* static */ void
|
|
gfxUserFontSet::UserFontCache::UpdateAllowedFontSets(
|
|
gfxUserFontSet* aUserFontSet)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!sUserFonts) {
|
|
return;
|
|
}
|
|
|
|
for (auto iter = sUserFonts->Iter(); !iter.Done(); iter.Next()) {
|
|
Entry* entry = iter.Get();
|
|
if (!entry->IsFontSetAllowedKnown(aUserFontSet)) {
|
|
gfxFontSrcPrincipal* principal = entry->GetPrincipal();
|
|
if (!principal) {
|
|
// This is a data: URI. Just get the standard principal the
|
|
// font set uses. (For cases when mUseOriginPrincipal is true,
|
|
// we don't use the cached results of IsFontLoadAllowed, and
|
|
// instead just process the data: URI load async.)
|
|
principal = aUserFontSet->GetStandardFontLoadPrincipal();
|
|
}
|
|
nsTArray<nsCOMPtr<nsIRunnable>> violations;
|
|
bool allowed =
|
|
aUserFontSet->IsFontLoadAllowed(entry->GetURI()->get(),
|
|
principal->get(),
|
|
&violations);
|
|
entry->SetIsFontSetAllowed(aUserFontSet, allowed, Move(violations));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */ void
|
|
gfxUserFontSet::UserFontCache::ClearAllowedFontSets(
|
|
gfxUserFontSet* aUserFontSet)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!sUserFonts) {
|
|
return;
|
|
}
|
|
|
|
for (auto iter = sUserFonts->Iter(); !iter.Done(); iter.Next()) {
|
|
Entry* entry = iter.Get();
|
|
entry->ClearIsFontSetAllowed(aUserFontSet);
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::Shutdown()
|
|
{
|
|
if (sUserFonts) {
|
|
delete sUserFonts;
|
|
sUserFonts = nullptr;
|
|
}
|
|
}
|
|
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)
|
|
|
|
bool
|
|
gfxUserFontSet::UserFontCache::Entry::CheckIsFontSetAllowed(
|
|
gfxUserFontSet* aUserFontSet) const
|
|
{
|
|
LoadResultEntry* entry = mAllowedFontSets.GetEntry(aUserFontSet);
|
|
MOZ_ASSERT(entry, "UpdateAllowedFontSets should have been called and "
|
|
"added an entry to mAllowedFontSets");
|
|
return entry->mAllowed;
|
|
}
|
|
|
|
bool
|
|
gfxUserFontSet::UserFontCache::Entry::CheckIsFontSetAllowedAndDispatchViolations(
|
|
gfxUserFontSet* aUserFontSet) const
|
|
{
|
|
LoadResultEntry* entry = mAllowedFontSets.GetEntry(aUserFontSet);
|
|
MOZ_ASSERT(entry, "UpdateAllowedFontSets should have been called and "
|
|
"added an entry to mAllowedFontSets");
|
|
if (!entry->mViolations.IsEmpty()) {
|
|
aUserFontSet->DispatchFontLoadViolations(entry->mViolations);
|
|
}
|
|
return entry->mAllowed;
|
|
}
|
|
|
|
bool
|
|
gfxUserFontSet::UserFontCache::Entry::IsFontSetAllowedKnown(
|
|
gfxUserFontSet* aUserFontSet) const
|
|
{
|
|
return mAllowedFontSets.Contains(aUserFontSet);
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::Entry::SetIsFontSetAllowed(
|
|
gfxUserFontSet* aUserFontSet,
|
|
bool aAllowed,
|
|
nsTArray<nsCOMPtr<nsIRunnable>>&& aViolations)
|
|
{
|
|
MOZ_ASSERT(!IsFontSetAllowedKnown(aUserFontSet));
|
|
LoadResultEntry* entry = mAllowedFontSets.PutEntry(aUserFontSet);
|
|
entry->mAllowed = aAllowed;
|
|
entry->mViolations.SwapElements(aViolations);
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::Entry::ClearIsFontSetAllowed(
|
|
gfxUserFontSet* aUserFontSet)
|
|
{
|
|
mAllowedFontSets.RemoveEntry(aUserFontSet);
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::Entry::ReportMemory(
|
|
nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
|
|
{
|
|
MOZ_ASSERT(mFontEntry);
|
|
nsAutoCString path("explicit/gfx/user-fonts/font(");
|
|
|
|
if (aAnonymize) {
|
|
path.AppendPrintf("<anonymized-%p>", this);
|
|
} else {
|
|
NS_ConvertUTF16toUTF8 familyName(mFontEntry->mFamilyName);
|
|
path.AppendPrintf("family=%s", familyName.get());
|
|
if (mURI) {
|
|
nsCString spec = mURI->GetSpecOrDefault();
|
|
spec.ReplaceChar('/', '\\');
|
|
// Some fonts are loaded using horrendously-long data: URIs;
|
|
// truncate those before reporting them.
|
|
bool isData;
|
|
if (NS_SUCCEEDED(mURI->get()->SchemeIs("data", &isData)) && isData &&
|
|
spec.Length() > 255) {
|
|
spec.Truncate(252);
|
|
spec.AppendLiteral("...");
|
|
}
|
|
path.AppendPrintf(", url=%s", spec.get());
|
|
}
|
|
if (mPrincipal) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
mPrincipal->get()->GetURI(getter_AddRefs(uri));
|
|
if (uri) {
|
|
nsCString spec = uri->GetSpecOrDefault();
|
|
if (!spec.IsEmpty()) {
|
|
// Include a clue as to who loaded this resource. (Note
|
|
// that because of font entry sharing, other pages may now
|
|
// be using this resource, and the original page may not
|
|
// even be loaded any longer.)
|
|
spec.ReplaceChar('/', '\\');
|
|
path.AppendPrintf(", principal=%s", spec.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
path.Append(')');
|
|
|
|
aHandleReport->Callback(
|
|
EmptyCString(), path,
|
|
nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
|
|
mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf),
|
|
NS_LITERAL_CSTRING("Memory used by @font-face resource."),
|
|
aData);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter,
|
|
nsIMemoryReporter)
|
|
|
|
NS_IMETHODIMP
|
|
gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports(
|
|
nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
|
|
{
|
|
if (!sUserFonts) {
|
|
return NS_OK;
|
|
}
|
|
|
|
for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
|
|
it.Get()->ReportMemory(aHandleReport, aData, aAnonymize);
|
|
}
|
|
|
|
MOZ_COLLECT_REPORT(
|
|
"explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES,
|
|
sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf),
|
|
"Memory used by the @font-face cache, not counting the actual font "
|
|
"resources.");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef DEBUG_USERFONT_CACHE
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::Entry::Dump()
|
|
{
|
|
nsresult rv;
|
|
|
|
nsAutoCString principalURISpec("(null)");
|
|
bool setDomain = false;
|
|
|
|
if (mPrincipal) {
|
|
nsCOMPtr<nsIURI> principalURI;
|
|
rv = mPrincipal->get()->GetURI(getter_AddRefs(principalURI));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
principalURI->GetSpec(principalURISpec);
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> domainURI;
|
|
mPrincipal->get()->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(),
|
|
NS_ConvertUTF16toUTF8(mFontEntry->FamilyName()).get(),
|
|
setDomain ? "true" : "false",
|
|
principalURISpec.get());
|
|
}
|
|
|
|
void
|
|
gfxUserFontSet::UserFontCache::Dump()
|
|
{
|
|
if (!sUserFonts) {
|
|
return;
|
|
}
|
|
|
|
printf("userfontcache dump count: %d ========\n", sUserFonts->Count());
|
|
for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
|
|
it.Get()->Dump();
|
|
}
|
|
printf("userfontcache dump ==================\n");
|
|
}
|
|
|
|
#endif
|
|
|
|
#undef LOG
|
|
#undef LOG_ENABLED
|