No bug - tagging 2af34b4c9adf8c8defd3251b569af9c38cc0a429 with FIREFOX_BETA_124_BASE...
[gecko.git] / gfx / thebes / gfxUserFontSet.cpp
blobdd1a287803225dc74a2a70258ca9689c83e63028
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Logging.h"
8 #include "gfxUserFontSet.h"
9 #include "gfxPlatform.h"
10 #include "gfxFontConstants.h"
11 #include "mozilla/Atomics.h"
12 #include "mozilla/FontPropertyTypes.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/ProfilerLabels.h"
15 #include "mozilla/Services.h"
16 #include "mozilla/StaticPrefs_gfx.h"
17 #include "mozilla/Telemetry.h"
18 #include "mozilla/gfx/2D.h"
19 #include "gfxPlatformFontList.h"
20 #include "mozilla/PostTraversalTask.h"
21 #include "gfxOTSUtils.h"
22 #include "nsIFontLoadCompleteCallback.h"
23 #include "nsProxyRelease.h"
24 #include "nsContentUtils.h"
25 #include "nsTHashSet.h"
27 using namespace mozilla;
29 mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() {
30 static LazyLogModule sLog("userfonts");
31 return sLog;
34 #define LOG(args) \
35 MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
36 #define LOG_ENABLED() \
37 MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug)
39 static Atomic<uint64_t> sFontSetGeneration(0);
41 gfxUserFontEntry::gfxUserFontEntry(nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
42 gfxUserFontAttributes&& aAttr)
43 : gfxFontEntry("userfont"_ns),
44 mUserFontLoadState(STATUS_NOT_LOADED),
45 mFontDataLoadingState(NOT_LOADING),
46 mSeenLocalSource(false),
47 mUnsupportedFormat(false),
48 mFontDisplay(aAttr.mFontDisplay),
49 mLoader(nullptr) {
50 mIsUserFontContainer = true;
51 mSrcList = std::move(aFontFaceSrcList);
52 mCurrentSrcIndex = 0;
53 mWeightRange = aAttr.mWeight;
54 mStretchRange = aAttr.mStretch;
55 mStyleRange = aAttr.mStyle;
56 mFeatureSettings = std::move(aAttr.mFeatureSettings);
57 mVariationSettings = std::move(aAttr.mVariationSettings);
58 mLanguageOverride = aAttr.mLanguageOverride;
59 SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges));
60 mRangeFlags = aAttr.mRangeFlags;
61 mAscentOverride = aAttr.mAscentOverride;
62 mDescentOverride = aAttr.mDescentOverride;
63 mLineGapOverride = aAttr.mLineGapOverride;
64 mSizeAdjust = aAttr.mSizeAdjust;
65 mFamilyName = aAttr.mFamilyName;
68 void gfxUserFontEntry::UpdateAttributes(gfxUserFontAttributes&& aAttr) {
69 MOZ_ASSERT(NS_IsMainThread());
71 // Remove the entry from the user font cache, if present there, as the cache
72 // key may no longer be correct with the new attributes.
73 gfxUserFontSet::UserFontCache::ForgetFont(this);
75 mFontDisplay = aAttr.mFontDisplay;
76 mWeightRange = aAttr.mWeight;
77 mStretchRange = aAttr.mStretch;
78 mStyleRange = aAttr.mStyle;
79 mFeatureSettings = std::move(aAttr.mFeatureSettings);
80 mVariationSettings = std::move(aAttr.mVariationSettings);
81 mLanguageOverride = aAttr.mLanguageOverride;
82 SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges));
83 mRangeFlags = aAttr.mRangeFlags;
84 mAscentOverride = aAttr.mAscentOverride;
85 mDescentOverride = aAttr.mDescentOverride;
86 mLineGapOverride = aAttr.mLineGapOverride;
87 mSizeAdjust = aAttr.mSizeAdjust;
90 gfxUserFontEntry::~gfxUserFontEntry() {
91 // Assert that we don't drop any gfxUserFontEntry objects during a Servo
92 // traversal, since PostTraversalTask objects can hold raw pointers to
93 // gfxUserFontEntry objects.
94 MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
97 bool gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
98 const gfxUserFontAttributes& aAttr) {
99 return mWeightRange == aAttr.mWeight && mStretchRange == aAttr.mStretch &&
100 mStyleRange == aAttr.mStyle &&
101 mFeatureSettings == aAttr.mFeatureSettings &&
102 mVariationSettings == aAttr.mVariationSettings &&
103 mLanguageOverride == aAttr.mLanguageOverride &&
104 mSrcList == aFontFaceSrcList && mFontDisplay == aAttr.mFontDisplay &&
105 mRangeFlags == aAttr.mRangeFlags &&
106 mAscentOverride == aAttr.mAscentOverride &&
107 mDescentOverride == aAttr.mDescentOverride &&
108 mLineGapOverride == aAttr.mLineGapOverride &&
109 mSizeAdjust == aAttr.mSizeAdjust &&
110 ((!aAttr.mUnicodeRanges && !mCharacterMap) ||
111 (aAttr.mUnicodeRanges && mCharacterMap &&
112 GetCharacterMap()->Equals(aAttr.mUnicodeRanges)));
115 gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) {
116 MOZ_ASSERT_UNREACHABLE(
117 "should only be creating a gfxFont"
118 " with an actual platform font entry");
120 // userfont entry is a container, can't create font from the container
121 return nullptr;
124 class MOZ_STACK_CLASS gfxOTSMessageContext : public gfxOTSContext {
125 public:
126 virtual ~gfxOTSMessageContext() {
127 MOZ_ASSERT(mMessages.IsEmpty(), "should have called TakeMessages");
130 virtual void Message(int level, const char* format,
131 ...) MSGFUNC_FMT_ATTR override {
132 va_list va;
134 // Special-case glyph bounding box warnings: collect all bad glyph IDs,
135 // so we can issue a single message at the end.
136 if (level > 0 && strstr(format, "bbox was incorrect")) {
137 // Extract the glyph ID from the message: it follows the last space in
138 // the message string.
139 const char* lastSpace = strrchr(format, ' ');
140 if (lastSpace) {
141 int gid = atoi(lastSpace + 1);
142 mBadBBoxGlyphs.AppendElement(gid);
144 return;
147 va_start(va, format);
149 nsCString msg;
150 msg.AppendVprintf(format, va);
152 va_end(va);
154 if (level > 0) {
155 // For warnings (rather than errors that cause the font to fail),
156 // we only report the first instance of any given message.
157 if (!mWarningsIssued.EnsureInserted(msg)) {
158 return;
162 mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, level});
165 bool Process(ots::OTSStream* aOutput, const uint8_t* aInput, size_t aLength,
166 nsTArray<gfxUserFontEntry::OTSMessage>& aMessages) {
167 bool ok = ots::OTSContext::Process(aOutput, aInput, aLength);
168 aMessages = TakeMessages();
169 return ok;
172 nsTArray<gfxUserFontEntry::OTSMessage>&& TakeMessages() {
173 if (!mBadBBoxGlyphs.IsEmpty()) {
174 nsAutoCString msg("Glyph bbox was incorrect (glyph ids");
175 for (const auto gid : mBadBBoxGlyphs) {
176 msg.Append(" ");
177 msg.AppendInt(gid);
179 msg.Append(")");
180 mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, 1});
181 mBadBBoxGlyphs.Clear();
183 return std::move(mMessages);
186 private:
187 nsTHashSet<nsCString> mWarningsIssued;
188 nsTArray<gfxUserFontEntry::OTSMessage> mMessages;
189 nsTArray<uint16_t> mBadBBoxGlyphs;
192 // Call the OTS library to sanitize an sfnt before attempting to use it.
193 // Returns a newly-allocated block, or nullptr in case of fatal errors.
194 const uint8_t* gfxUserFontEntry::SanitizeOpenTypeData(
195 const uint8_t* aData, uint32_t aLength, uint32_t& aSanitaryLength,
196 gfxUserFontType& aFontType, nsTArray<OTSMessage>& aMessages) {
197 aFontType = gfxFontUtils::DetermineFontDataType(aData, aLength);
198 Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(aFontType));
200 size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(aLength, aFontType);
201 if (!lengthHint) {
202 aSanitaryLength = 0;
203 return nullptr;
206 gfxOTSExpandingMemoryStream<gfxOTSMozAlloc> output(lengthHint);
208 gfxOTSMessageContext otsContext;
209 if (!otsContext.Process(&output, aData, aLength, aMessages)) {
210 // Failed to decode/sanitize the font, so discard it.
211 aSanitaryLength = 0;
212 return nullptr;
215 aSanitaryLength = output.Tell();
216 return static_cast<const uint8_t*>(output.forget());
219 void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
220 uint32_t aSrcIndex, bool aPrivate,
221 const nsACString& aOriginalName,
222 FallibleTArray<uint8_t>* aMetadata,
223 uint32_t aMetaOrigLen,
224 uint8_t aCompression) {
225 if (!aFontEntry->mUserFontData) {
226 aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>();
228 gfxUserFontData* userFontData = aFontEntry->mUserFontData.get();
229 userFontData->mSrcIndex = aSrcIndex;
230 const gfxFontFaceSrc& src = mSrcList[aSrcIndex];
231 switch (src.mSourceType) {
232 case gfxFontFaceSrc::eSourceType_Local:
233 userFontData->mLocalName = src.mLocalName;
234 break;
235 case gfxFontFaceSrc::eSourceType_URL:
236 userFontData->mURI = src.mURI;
237 userFontData->mPrincipal = mPrincipal;
238 break;
239 case gfxFontFaceSrc::eSourceType_Buffer:
240 userFontData->mIsBuffer = true;
241 break;
243 userFontData->mPrivate = aPrivate;
244 userFontData->mTechFlags = src.mTechFlags;
245 userFontData->mFormatHint = src.mFormatHint;
246 userFontData->mRealName = aOriginalName;
247 if (aMetadata) {
248 userFontData->mMetadata = std::move(*aMetadata);
249 userFontData->mMetaOrigLen = aMetaOrigLen;
250 userFontData->mCompression = aCompression;
254 size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
255 return aMallocSizeOf(this) +
256 mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) +
257 mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
258 mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
259 // Not counting mURI and mPrincipal, as those will be shared.
262 /*virtual*/
263 gfxUserFontFamily::~gfxUserFontFamily() {
264 // Should not be dropped by stylo
265 MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
268 already_AddRefed<gfxFontSrcPrincipal> gfxFontFaceSrc::LoadPrincipal(
269 const gfxUserFontSet& aFontSet) const {
270 MOZ_ASSERT(mSourceType == eSourceType_URL);
271 if (mUseOriginPrincipal) {
272 MOZ_ASSERT(mOriginPrincipal);
273 return RefPtr{mOriginPrincipal}.forget();
275 return aFontSet.GetStandardFontLoadPrincipal();
278 void gfxUserFontEntry::GetFamilyNameAndURIForLogging(uint32_t aSrcIndex,
279 nsACString& aFamilyName,
280 nsACString& aURI) {
281 aFamilyName = mFamilyName;
283 aURI.Truncate();
284 if (aSrcIndex >= mSrcList.Length()) {
285 aURI.AppendLiteral("(end of source list)");
286 } else {
287 if (mSrcList[aSrcIndex].mURI) {
288 mSrcList[aSrcIndex].mURI->GetSpec(aURI);
289 // If the source URI was very long, elide the middle of it.
290 // In principle, the byte-oriented chopping here could leave us
291 // with partial UTF-8 characters at the point where we cut it,
292 // but it really doesn't matter as this is just for logging.
293 const uint32_t kMaxURILengthForLogging = 256;
294 // UTF-8 ellipsis, with spaces to allow additional wrap opportunities
295 // in the resulting log message
296 const char kEllipsis[] = {' ', '\xE2', '\x80', '\xA6', ' '};
297 if (aURI.Length() > kMaxURILengthForLogging) {
298 aURI.Replace(kMaxURILengthForLogging / 2,
299 aURI.Length() - kMaxURILengthForLogging, kEllipsis,
300 ArrayLength(kEllipsis));
302 } else {
303 aURI.AppendLiteral("(invalid URI)");
308 struct WOFFHeader {
309 AutoSwap_PRUint32 signature;
310 AutoSwap_PRUint32 flavor;
311 AutoSwap_PRUint32 length;
312 AutoSwap_PRUint16 numTables;
313 AutoSwap_PRUint16 reserved;
314 AutoSwap_PRUint32 totalSfntSize;
315 AutoSwap_PRUint16 majorVersion;
316 AutoSwap_PRUint16 minorVersion;
317 AutoSwap_PRUint32 metaOffset;
318 AutoSwap_PRUint32 metaCompLen;
319 AutoSwap_PRUint32 metaOrigLen;
320 AutoSwap_PRUint32 privOffset;
321 AutoSwap_PRUint32 privLen;
324 struct WOFF2Header {
325 AutoSwap_PRUint32 signature;
326 AutoSwap_PRUint32 flavor;
327 AutoSwap_PRUint32 length;
328 AutoSwap_PRUint16 numTables;
329 AutoSwap_PRUint16 reserved;
330 AutoSwap_PRUint32 totalSfntSize;
331 AutoSwap_PRUint32 totalCompressedSize;
332 AutoSwap_PRUint16 majorVersion;
333 AutoSwap_PRUint16 minorVersion;
334 AutoSwap_PRUint32 metaOffset;
335 AutoSwap_PRUint32 metaCompLen;
336 AutoSwap_PRUint32 metaOrigLen;
337 AutoSwap_PRUint32 privOffset;
338 AutoSwap_PRUint32 privLen;
341 template <typename HeaderT>
342 void CopyWOFFMetadata(const uint8_t* aFontData, uint32_t aLength,
343 FallibleTArray<uint8_t>* aMetadata,
344 uint32_t* aMetaOrigLen) {
345 // This function may be called with arbitrary, unvalidated "font" data
346 // from @font-face, so it needs to be careful to bounds-check, etc.,
347 // before trying to read anything.
348 // This just saves a copy of the compressed data block; it does NOT check
349 // that the block can be successfully decompressed, or that it contains
350 // well-formed/valid XML metadata.
351 if (aLength < sizeof(HeaderT)) {
352 return;
354 const HeaderT* woff = reinterpret_cast<const HeaderT*>(aFontData);
355 uint32_t metaOffset = woff->metaOffset;
356 uint32_t metaCompLen = woff->metaCompLen;
357 if (!metaOffset || !metaCompLen || !woff->metaOrigLen) {
358 return;
360 if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) {
361 return;
363 if (!aMetadata->SetLength(woff->metaCompLen, fallible)) {
364 return;
366 memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen);
367 *aMetaOrigLen = woff->metaOrigLen;
370 void gfxUserFontEntry::LoadNextSrc() {
371 NS_ASSERTION(mCurrentSrcIndex < mSrcList.Length(),
372 "already at the end of the src list for user font");
373 NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
374 mUserFontLoadState == STATUS_LOAD_PENDING ||
375 mUserFontLoadState == STATUS_LOADING) &&
376 mFontDataLoadingState < LOADING_FAILED,
377 "attempting to load a font that has either completed or failed");
379 if (mUserFontLoadState == STATUS_NOT_LOADED) {
380 SetLoadState(STATUS_LOADING);
381 mFontDataLoadingState = LOADING_STARTED;
382 mUnsupportedFormat = false;
383 } else {
384 // we were already loading; move to the next source,
385 // but don't reset state - if we've already timed out,
386 // that counts against the new download
387 mCurrentSrcIndex++;
390 DoLoadNextSrc(false);
393 void gfxUserFontEntry::ContinueLoad() {
394 MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING);
395 MOZ_ASSERT(mSrcList[mCurrentSrcIndex].mSourceType ==
396 gfxFontFaceSrc::eSourceType_URL);
398 SetLoadState(STATUS_LOADING);
399 DoLoadNextSrc(/* aIsContinue = */ true);
400 if (LoadState() != STATUS_LOADING) {
401 MOZ_ASSERT(mUserFontLoadState != STATUS_LOAD_PENDING,
402 "Not in parallel traversal, shouldn't get LOAD_PENDING again");
403 // Loading is synchronously finished (loaded from cache or failed). We
404 // need to increment the generation so that we flush the style data to
405 // use the new loaded font face.
406 // Without parallel traversal, we would simply get the right font data
407 // after the first call to DoLoadNextSrc() in this case, so we don't need
408 // to touch the generation to trigger another restyle.
409 // XXX We may want to return synchronously in parallel traversal in those
410 // cases as well if possible, so that we don't have an additional restyle.
411 // That doesn't work currently because Document::GetDocShell (called from
412 // FontFaceSet::CheckFontLoad) dereferences a weak pointer, which is not
413 // allowed in parallel traversal.
414 IncrementGeneration();
418 static bool IgnorePrincipal(gfxFontSrcURI* aURI) {
419 return aURI->InheritsSecurityContext();
422 void gfxUserFontEntry::DoLoadNextSrc(bool aIsContinue) {
423 RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
424 if (NS_WARN_IF(!fontSet)) {
425 LOG(("userfonts (%p) failed expired font set for (%s)\n", fontSet.get(),
426 mFamilyName.get()));
427 mFontDataLoadingState = LOADING_FAILED;
428 SetLoadState(STATUS_FAILED);
429 return;
432 uint32_t numSrc = mSrcList.Length();
434 // load each src entry in turn, until a local face is found
435 // or a download begins successfully
436 while (mCurrentSrcIndex < numSrc) {
437 gfxFontFaceSrc& currSrc = mSrcList[mCurrentSrcIndex];
439 // src local ==> lookup and load immediately
441 if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) {
442 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
443 pfl->AddUserFontSet(fontSet);
444 // Don't look up local fonts if the font whitelist is being used.
445 gfxFontEntry* fe = nullptr;
446 if (!pfl->IsFontFamilyWhitelistActive()) {
447 fe = gfxPlatform::GetPlatform()->LookupLocalFont(
448 fontSet->GetPresContext(), currSrc.mLocalName, Weight(), Stretch(),
449 SlantStyle());
450 // Note that we've attempted a local lookup, even if it failed,
451 // as this means we are dependent on any updates to the font list.
452 mSeenLocalSource = true;
453 nsTArray<RefPtr<gfxUserFontSet>> fontSets;
454 GetUserFontSets(fontSets);
455 for (gfxUserFontSet* fontSet : fontSets) {
456 // We need to note on each gfxUserFontSet that contains the user
457 // font entry that we used a local() rule.
458 fontSet->SetLocalRulesUsed();
461 if (fe) {
462 LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n",
463 fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(),
464 mFamilyName.get(), uint32_t(fontSet->mGeneration)));
465 fe->mFeatureSettings.AppendElements(mFeatureSettings);
466 fe->mVariationSettings.AppendElements(mVariationSettings);
467 fe->mLanguageOverride = mLanguageOverride;
468 fe->mFamilyName = mFamilyName;
469 fe->mRangeFlags = mRangeFlags;
470 fe->mAscentOverride = mAscentOverride;
471 fe->mDescentOverride = mDescentOverride;
472 fe->mLineGapOverride = mLineGapOverride;
473 fe->mSizeAdjust = mSizeAdjust;
474 // For src:local(), we don't care whether the request is from
475 // a private window as there's no issue of caching resources;
476 // local fonts are just available all the time.
477 StoreUserFontData(fe, mCurrentSrcIndex, false, nsCString(), nullptr, 0,
478 gfxUserFontData::kUnknownCompression);
479 mPlatformFontEntry = fe;
480 SetLoadState(STATUS_LOADED);
481 Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
482 currSrc.mSourceType + 1);
483 return;
485 LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n",
486 fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(),
487 mFamilyName.get()));
490 // src url ==> start the load process
491 else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) {
492 if (gfxPlatform::GetPlatform()->IsFontFormatSupported(
493 currSrc.mFormatHint, currSrc.mTechFlags)) {
494 if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
495 // Only support style worker threads synchronously getting
496 // entries from the font cache when it's not a data: URI
497 // @font-face that came from UA or user sheets, since we
498 // were not able to call IsFontLoadAllowed ahead of time
499 // for these entries.
500 if (currSrc.mUseOriginPrincipal && IgnorePrincipal(currSrc.mURI)) {
501 set->AppendTask(PostTraversalTask::LoadFontEntry(this));
502 SetLoadState(STATUS_LOAD_PENDING);
503 return;
507 // see if we have an existing entry for this source
508 gfxFontEntry* fe =
509 gfxUserFontSet::UserFontCache::GetFont(currSrc, *this);
510 if (fe) {
511 mPlatformFontEntry = fe;
512 SetLoadState(STATUS_LOADED);
513 LOG(
514 ("userfonts (%p) [src %d] "
515 "loaded uri from cache: (%s) for (%s)\n",
516 fontSet.get(), mCurrentSrcIndex,
517 currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get()));
518 return;
521 if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
522 // If we need to start a font load and we're on a style
523 // worker thread, we have to defer it.
524 set->AppendTask(PostTraversalTask::LoadFontEntry(this));
525 SetLoadState(STATUS_LOAD_PENDING);
526 return;
529 // record the principal we should use for the load for use when
530 // creating a channel and when caching the loaded entry.
531 mPrincipal = currSrc.LoadPrincipal(*fontSet);
533 const bool loadDoesntSpin =
534 !aIsContinue && currSrc.mURI->SyncLoadIsOK();
535 if (loadDoesntSpin) {
536 uint8_t* buffer = nullptr;
537 uint32_t bufferLength = 0;
539 // sync load font immediately
540 nsresult rv =
541 fontSet->SyncLoadFontData(this, &currSrc, buffer, bufferLength);
543 if (NS_SUCCEEDED(rv) &&
544 LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) {
545 SetLoadState(STATUS_LOADED);
546 Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
547 currSrc.mSourceType + 1);
548 return;
550 fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed",
551 nsIScriptError::errorFlag, rv);
552 } else if (!aIsContinue) {
553 RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
554 "gfxUserFontSet::AsyncContinueLoad",
555 [loader = RefPtr{this}] { loader->ContinueLoad(); });
556 SetLoadState(STATUS_LOAD_PENDING);
557 // We don't want to trigger the channel open at random points in
558 // time, because it can run privileged JS.
559 if (!nsContentUtils::IsSafeToRunScript()) {
560 // There's a script-blocker on the stack. We know the sooner point
561 // where we can trigger the load.
562 nsContentUtils::AddScriptRunner(runnable.forget());
563 } else {
564 // We dispatch with a rather high priority, since somebody actually
565 // cares about this font.
566 NS_DispatchToCurrentThreadQueue(runnable.forget(),
567 EventQueuePriority::MediumHigh);
569 return;
570 } else {
571 // Actually start the async load.
572 nsresult rv = fontSet->StartLoad(this, mCurrentSrcIndex);
573 if (NS_SUCCEEDED(rv)) {
574 LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n",
575 fontSet.get(), mCurrentSrcIndex,
576 currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get()));
577 return;
579 fontSet->LogMessage(this, mCurrentSrcIndex,
580 "failed to start download",
581 nsIScriptError::errorFlag, rv);
583 } else {
584 // We don't log a warning to the web console yet,
585 // as another source may load successfully
586 mUnsupportedFormat = true;
588 } else {
589 // FontFace buffer ==> load immediately
590 MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer);
592 uint8_t* buffer = nullptr;
593 uint32_t bufferLength = 0;
595 // sync load font immediately
596 currSrc.mBuffer->TakeBuffer(buffer, bufferLength);
597 if (buffer &&
598 LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) {
599 // LoadPlatformFontSync takes ownership of the buffer, so no need
600 // to free it here.
601 SetLoadState(STATUS_LOADED);
602 Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
603 currSrc.mSourceType + 1);
604 return;
606 fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed",
607 nsIScriptError::errorFlag);
610 mCurrentSrcIndex++;
613 if (mUnsupportedFormat) {
614 fontSet->LogMessage(this, mCurrentSrcIndex, "no supported format found",
615 nsIScriptError::warningFlag);
618 // all src's failed; mark this entry as unusable (so fallback will occur)
619 LOG(("userfonts (%p) failed all src for (%s)\n", fontSet.get(),
620 mFamilyName.get()));
621 mFontDataLoadingState = LOADING_FAILED;
622 SetLoadState(STATUS_FAILED);
625 void gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState) {
626 mUserFontLoadState = aLoadState;
629 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)
631 bool gfxUserFontEntry::LoadPlatformFontSync(uint32_t aSrcIndex,
632 const uint8_t* aFontData,
633 uint32_t aLength) {
634 AUTO_PROFILER_LABEL("gfxUserFontEntry::LoadPlatformFontSync", OTHER);
635 NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
636 mUserFontLoadState == STATUS_LOAD_PENDING ||
637 mUserFontLoadState == STATUS_LOADING) &&
638 mFontDataLoadingState < LOADING_FAILED,
639 "attempting to load a font that has either completed or failed");
641 // Unwrap/decompress/sanitize or otherwise munge the downloaded data
642 // to make a usable sfnt structure.
644 // Call the OTS sanitizer; this will also decode WOFF to sfnt
645 // if necessary. The original data in aFontData is left unchanged.
646 uint32_t sanitaryLen;
647 gfxUserFontType fontType;
648 nsTArray<OTSMessage> messages;
649 const uint8_t* sanitaryData =
650 SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages);
652 return LoadPlatformFont(aSrcIndex, aFontData, aLength, fontType, sanitaryData,
653 sanitaryLen, std::move(messages));
656 void gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread(
657 uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
658 nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) {
659 MOZ_ASSERT(!NS_IsMainThread());
661 uint32_t sanitaryLen;
662 gfxUserFontType fontType;
663 nsTArray<OTSMessage> messages;
664 const uint8_t* sanitaryData =
665 SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages);
667 nsCOMPtr<nsIRunnable> event =
668 NewRunnableMethod<uint32_t, const uint8_t*, uint32_t, gfxUserFontType,
669 const uint8_t*, uint32_t, nsTArray<OTSMessage>&&,
670 nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>(
671 "gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread", this,
672 &gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread, aSrcIndex,
673 aFontData, aLength, fontType, sanitaryData, sanitaryLen,
674 std::move(messages), aCallback);
675 NS_DispatchToMainThread(event.forget());
678 bool gfxUserFontEntry::LoadPlatformFont(uint32_t aSrcIndex,
679 const uint8_t* aOriginalFontData,
680 uint32_t aOriginalLength,
681 gfxUserFontType aFontType,
682 const uint8_t* aSanitizedFontData,
683 uint32_t aSanitizedLength,
684 nsTArray<OTSMessage>&& aMessages) {
685 MOZ_ASSERT(NS_IsMainThread());
686 RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
687 if (NS_WARN_IF(!fontSet)) {
688 free((void*)aOriginalFontData);
689 free((void*)aSanitizedFontData);
690 return false;
693 for (const auto& msg : aMessages) {
694 fontSet->LogMessage(this, aSrcIndex, msg.mMessage.get(),
695 msg.mLevel > 0 ? nsIScriptError::warningFlag
696 : nsIScriptError::errorFlag);
699 if (!aSanitizedFontData) {
700 fontSet->LogMessage(this, aSrcIndex, "rejected by sanitizer");
701 } else {
702 // Check whether aSanitizedFontData is a known OpenType format; it might be
703 // a TrueType Collection, which OTS would accept but we don't yet
704 // know how to handle. If so, discard.
705 if (gfxFontUtils::DetermineFontDataType(
706 aSanitizedFontData, aSanitizedLength) != GFX_USERFONT_OPENTYPE) {
707 fontSet->LogMessage(this, aSrcIndex, "not a supported OpenType format");
708 free((void*)aSanitizedFontData);
709 aSanitizedFontData = nullptr;
713 // Because platform font activation code may replace the name table
714 // in the font with a synthetic one, we save the original name so that
715 // it can be reported via the InspectorUtils API.
716 nsAutoCString originalFullName;
718 gfxFontEntry* fe = nullptr;
719 uint32_t fontCompressionRatio = 0;
720 size_t computedSize = 0;
722 if (aSanitizedFontData) {
723 if (aSanitizedLength) {
724 fontCompressionRatio =
725 uint32_t(100.0 * aOriginalLength / aSanitizedLength + 0.5);
726 if (aFontType == GFX_USERFONT_WOFF || aFontType == GFX_USERFONT_WOFF2) {
727 Telemetry::Accumulate(aFontType == GFX_USERFONT_WOFF
728 ? Telemetry::WEBFONT_COMPRESSION_WOFF
729 : Telemetry::WEBFONT_COMPRESSION_WOFF2,
730 fontCompressionRatio);
734 // The sanitizer ensures that we have a valid sfnt and a usable
735 // name table, so this should never fail unless we're out of
736 // memory, and GetFullNameFromSFNT is not directly exposed to
737 // arbitrary/malicious data from the web.
738 gfxFontUtils::GetFullNameFromSFNT(aSanitizedFontData, aSanitizedLength,
739 originalFullName);
741 // Record size for memory reporting purposes. We measure this now
742 // because by the time we potentially want to collect reports, this
743 // data block may have been handed off to opaque OS font APIs that
744 // don't allow us to retrieve or measure it directly.
745 // The *OnAlloc function will also tell DMD about this block, as the
746 // OS font code may hold on to it for an extended period.
747 computedSize = UserFontMallocSizeOfOnAlloc(aSanitizedFontData);
749 // Here ownership of aSanitizedFontData is passed to the platform,
750 // which will delete it when no longer required
751 fe = gfxPlatform::GetPlatform()->MakePlatformFont(
752 mName, Weight(), Stretch(), SlantStyle(), aSanitizedFontData,
753 aSanitizedLength);
754 if (!fe) {
755 fontSet->LogMessage(this, aSrcIndex, "not usable by platform");
759 if (fe) {
760 fe->mComputedSizeOfUserFont = computedSize;
762 // Save a copy of the metadata block (if present) for InspectorUtils
763 // to use if required. Ownership of the metadata block will be passed
764 // to the gfxUserFontData record below.
765 FallibleTArray<uint8_t> metadata;
766 uint32_t metaOrigLen = 0;
767 uint8_t compression = gfxUserFontData::kUnknownCompression;
768 if (aFontType == GFX_USERFONT_WOFF) {
769 CopyWOFFMetadata<WOFFHeader>(aOriginalFontData, aOriginalLength,
770 &metadata, &metaOrigLen);
771 compression = gfxUserFontData::kZlibCompression;
772 } else if (aFontType == GFX_USERFONT_WOFF2) {
773 CopyWOFFMetadata<WOFF2Header>(aOriginalFontData, aOriginalLength,
774 &metadata, &metaOrigLen);
775 compression = gfxUserFontData::kBrotliCompression;
778 // copy OpenType feature/language settings from the userfont entry to the
779 // newly-created font entry
780 fe->mFeatureSettings.AppendElements(mFeatureSettings);
781 fe->mVariationSettings.AppendElements(mVariationSettings);
782 fe->mLanguageOverride = mLanguageOverride;
783 fe->mFamilyName = mFamilyName;
784 fe->mRangeFlags = mRangeFlags;
785 fe->mAscentOverride = mAscentOverride;
786 fe->mDescentOverride = mDescentOverride;
787 fe->mLineGapOverride = mLineGapOverride;
788 fe->mSizeAdjust = mSizeAdjust;
789 StoreUserFontData(fe, aSrcIndex, fontSet->GetPrivateBrowsing(),
790 originalFullName, &metadata, metaOrigLen, compression);
791 LOG(
792 ("userfonts (%p) [src %d] loaded uri: (%s) for (%s) "
793 "(%p) gen: %8.8x compress: %d%%\n",
794 fontSet.get(), aSrcIndex,
795 mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get(),
796 this, uint32_t(fontSet->mGeneration), fontCompressionRatio));
797 mPlatformFontEntry = fe;
798 SetLoadState(STATUS_LOADED);
799 gfxUserFontSet::UserFontCache::CacheFont(fe);
800 } else {
801 LOG((
802 "userfonts (%p) [src %d] failed uri: (%s) for (%s)"
803 " error making platform font\n",
804 fontSet.get(), aSrcIndex,
805 mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get()));
808 // The downloaded data can now be discarded; the font entry is using the
809 // sanitized copy
810 free((void*)aOriginalFontData);
812 return fe != nullptr;
815 void gfxUserFontEntry::Load() {
816 if (mUserFontLoadState == STATUS_NOT_LOADED) {
817 LoadNextSrc();
821 void gfxUserFontEntry::IncrementGeneration() {
822 nsTArray<RefPtr<gfxUserFontSet>> fontSets;
823 GetUserFontSets(fontSets);
824 for (gfxUserFontSet* fontSet : fontSets) {
825 fontSet->IncrementGeneration();
829 // This is called when a font download finishes.
830 // Ownership of aFontData passes in here, and the font set must
831 // ensure that it is eventually deleted via free().
832 void gfxUserFontEntry::FontDataDownloadComplete(
833 uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
834 nsresult aDownloadStatus, nsIFontLoadCompleteCallback* aCallback) {
835 MOZ_ASSERT(NS_IsMainThread());
837 // forget about the loader, as we no longer potentially need to cancel it
838 // if the entry is obsoleted
839 mLoader = nullptr;
841 // download successful, make platform font using font data
842 if (NS_SUCCEEDED(aDownloadStatus) &&
843 mFontDataLoadingState != LOADING_TIMED_OUT) {
844 LoadPlatformFontAsync(aSrcIndex, aFontData, aLength, aCallback);
845 return;
848 RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
849 if (fontSet) {
850 // download failed or font-display timeout passed
851 if (mFontDataLoadingState == LOADING_TIMED_OUT) {
852 fontSet->LogMessage(this, aSrcIndex,
853 "font-display timeout, webfont not used",
854 nsIScriptError::infoFlag, aDownloadStatus);
855 } else {
856 fontSet->LogMessage(this, aSrcIndex, "download failed",
857 nsIScriptError::errorFlag, aDownloadStatus);
861 if (aFontData) {
862 free((void*)aFontData);
865 FontLoadFailed(aCallback);
868 void gfxUserFontEntry::LoadPlatformFontAsync(
869 uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
870 nsIFontLoadCompleteCallback* aCallback) {
871 nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> cb(
872 new nsMainThreadPtrHolder<nsIFontLoadCompleteCallback>("FontLoader",
873 aCallback));
875 // Do the OpenType sanitization over on the font loading thread. Once that is
876 // complete, we'll continue in ContinuePlatformFontLoadOnMainThread.
878 // We hold a strong reference to the gfxUserFontSet during this work, since
879 // the document might be closed while we are OMT, and release it at the end
880 // of ContinuePlatformFontLoadOnMainThread.
882 // If the set has already been freed, then the loading will fail when we
883 // resume on the main thread.
885 MOZ_ASSERT(!mLoadingFontSet);
886 mLoadingFontSet = GetUserFontSet();
888 nsCOMPtr<nsIRunnable> event =
889 NewRunnableMethod<uint32_t, const uint8_t*, uint32_t,
890 nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>(
891 "gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread", this,
892 &gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread, aSrcIndex,
893 aFontData, aLength, cb);
894 MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(event.forget()));
897 void gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread(
898 uint32_t aSrcIndex, const uint8_t* aOriginalFontData,
899 uint32_t aOriginalLength, gfxUserFontType aFontType,
900 const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength,
901 nsTArray<OTSMessage>&& aMessages,
902 nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) {
903 MOZ_ASSERT(NS_IsMainThread());
905 bool loaded = LoadPlatformFont(aSrcIndex, aOriginalFontData, aOriginalLength,
906 aFontType, aSanitizedFontData,
907 aSanitizedLength, std::move(aMessages));
908 aOriginalFontData = nullptr;
909 aSanitizedFontData = nullptr;
911 if (loaded) {
912 IncrementGeneration();
913 aCallback->FontLoadComplete();
914 } else {
915 FontLoadFailed(aCallback);
918 // Set in LoadPlatformFontAsync. If it is null, then the font set should have
919 // already been freed and we would not succeed in loading the font.
920 MOZ_ASSERT_IF(loaded, mLoadingFontSet);
921 mLoadingFontSet = nullptr;
924 void gfxUserFontEntry::FontLoadFailed(nsIFontLoadCompleteCallback* aCallback) {
925 MOZ_ASSERT(NS_IsMainThread());
927 // Error occurred. Make sure the FontFace's promise is rejected if the
928 // load timed out, or else load the next src.
929 if (mFontDataLoadingState == LOADING_TIMED_OUT) {
930 mFontDataLoadingState = LOADING_FAILED;
931 SetLoadState(STATUS_FAILED);
932 } else {
933 LoadNextSrc();
936 // We ignore the status returned by LoadNext();
937 // even if loading failed, we need to bump the font-set generation
938 // and return true in order to trigger reflow, so that fallback
939 // will be used where the text was "masked" by the pending download
940 IncrementGeneration();
941 aCallback->FontLoadComplete();
944 void gfxUserFontEntry::GetUserFontSets(
945 nsTArray<RefPtr<gfxUserFontSet>>& aResult) {
946 aResult.Clear();
947 RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
948 if (fontSet) {
949 aResult.AppendElement(std::move(fontSet));
953 gfxUserFontSet::gfxUserFontSet()
954 : mFontFamilies(4),
955 mRebuildGeneration(0),
956 mLocalRulesUsed(false),
957 mRebuildLocalRules(false),
958 mDownloadCount(0),
959 mDownloadSize(0) {
960 IncrementGeneration(true);
963 gfxUserFontSet::~gfxUserFontSet() { Destroy(); }
965 void gfxUserFontSet::Destroy() {
966 if (auto* pfl = gfxPlatformFontList::PlatformFontList(false)) {
967 pfl->RemoveUserFontSet(this);
970 mFontFamilies.Clear();
973 already_AddRefed<gfxUserFontEntry> gfxUserFontSet::FindOrCreateUserFontEntry(
974 nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
975 gfxUserFontAttributes&& aAttr) {
976 RefPtr<gfxUserFontEntry> entry;
978 // If there's already a userfont entry in the family whose descriptors all
979 // match, we can just move it to the end of the list instead of adding a new
980 // face that will always "shadow" the old one.
981 // Note that we can't do this for platform font entries, even if the
982 // style descriptors match, as they might have had a different source list,
983 // but we no longer have the old source list available to check.
984 RefPtr<gfxUserFontFamily> family = LookupFamily(aAttr.mFamilyName);
985 if (family) {
986 entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aAttr);
989 if (!entry) {
990 entry = CreateUserFontEntry(std::move(aFontFaceSrcList), std::move(aAttr));
993 return entry.forget();
996 gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry(
997 gfxUserFontFamily* aFamily,
998 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
999 const gfxUserFontAttributes& aAttr) {
1000 aFamily->ReadLock();
1001 const auto& fontList = aFamily->GetFontList();
1002 gfxUserFontEntry* result = nullptr;
1004 for (const auto& font : fontList) {
1005 if (!font->mIsUserFontContainer) {
1006 continue;
1009 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(font.get());
1010 if (ufe->Matches(aFontFaceSrcList, aAttr)) {
1011 result = ufe;
1012 break;
1015 aFamily->ReadUnlock();
1017 return result;
1020 void gfxUserFontSet::AddUserFontEntry(const nsCString& aFamilyName,
1021 gfxUserFontEntry* aUserFontEntry) {
1022 RefPtr<gfxUserFontFamily> family = GetFamily(aFamilyName);
1023 family->AddFontEntry(aUserFontEntry);
1025 if (LOG_ENABLED()) {
1026 nsAutoCString weightString;
1027 aUserFontEntry->Weight().ToString(weightString);
1028 nsAutoCString stretchString;
1029 aUserFontEntry->Stretch().ToString(stretchString);
1030 LOG(
1031 ("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s "
1032 "stretch: %s display: %d",
1033 this, aFamilyName.get(), aUserFontEntry,
1034 (aUserFontEntry->IsItalic()
1035 ? "italic"
1036 : (aUserFontEntry->IsOblique() ? "oblique" : "normal")),
1037 weightString.get(), stretchString.get(),
1038 static_cast<int>(aUserFontEntry->GetFontDisplay())));
1042 void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) {
1043 // add one, increment again if zero
1044 do {
1045 mGeneration = ++sFontSetGeneration;
1046 } while (mGeneration == 0);
1047 if (aIsRebuild) {
1048 mRebuildGeneration = mGeneration;
1052 void gfxUserFontSet::RebuildLocalRules() {
1053 if (mLocalRulesUsed) {
1054 mRebuildLocalRules = true;
1055 DoRebuildUserFontSet();
1059 already_AddRefed<gfxUserFontFamily> gfxUserFontSet::LookupFamily(
1060 const nsACString& aFamilyName) const {
1061 nsAutoCString key(aFamilyName);
1062 ToLowerCase(key);
1064 return mFontFamilies.Get(key);
1067 already_AddRefed<gfxUserFontFamily> gfxUserFontSet::GetFamily(
1068 const nsACString& aFamilyName) {
1069 nsAutoCString key(aFamilyName);
1070 ToLowerCase(key);
1072 return do_AddRef(mFontFamilies.GetOrInsertNew(key, aFamilyName));
1075 void gfxUserFontSet::ForgetLocalFaces() {
1076 for (const auto& fam : mFontFamilies.Values()) {
1077 ForgetLocalFace(fam);
1081 void gfxUserFontSet::ForgetLocalFace(gfxUserFontFamily* aFontFamily) {
1082 aFontFamily->ReadLock();
1083 const auto& fonts = aFontFamily->GetFontList();
1084 for (const auto& f : fonts) {
1085 auto ufe = static_cast<gfxUserFontEntry*>(f.get());
1086 // If the user font entry has loaded an entry using src:local(),
1087 // discard it as no longer valid.
1088 if (ufe->GetPlatformFontEntry() &&
1089 ufe->GetPlatformFontEntry()->IsLocalUserFont()) {
1090 ufe->mPlatformFontEntry = nullptr;
1092 // We need to re-evaluate the source list in the context of the new
1093 // platform fontlist, whether or not the entry actually used a local()
1094 // source last time, as one might be newly available.
1095 if (ufe->mSeenLocalSource) {
1096 ufe->LoadCanceled();
1099 aFontFamily->ReadUnlock();
1102 ///////////////////////////////////////////////////////////////////////////////
1103 // gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts
1104 // across pages/fontsets rather than instantiating new platform fonts.
1106 // Entries are added to this cache when a platform font is instantiated from
1107 // downloaded data, and removed when the platform font entry is destroyed.
1108 // We don't need to use a timed expiration scheme here because the gfxFontEntry
1109 // for a downloaded font will be kept alive by its corresponding gfxFont
1110 // instance(s) until they are deleted, and *that* happens using an expiration
1111 // tracker (gfxFontCache). The result is that the downloaded font instances
1112 // recorded here will persist between pages and can get reused (provided the
1113 // source URI and principal match, of course).
1114 ///////////////////////////////////////////////////////////////////////////////
1116 nsTHashtable<gfxUserFontSet::UserFontCache::Entry>*
1117 gfxUserFontSet::UserFontCache::sUserFonts = nullptr;
1119 NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver)
1121 NS_IMETHODIMP
1122 gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject,
1123 const char* aTopic,
1124 const char16_t* aData) {
1125 if (!sUserFonts) {
1126 return NS_OK;
1129 if (!strcmp(aTopic, "cacheservice:empty-cache")) {
1130 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1131 i.Remove();
1133 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1134 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1135 if (i.Get()->IsPrivate()) {
1136 i.Remove();
1139 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
1140 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1141 i.Get()->GetFontEntry()->DisconnectSVG();
1143 } else {
1144 MOZ_ASSERT_UNREACHABLE("unexpected topic");
1147 return NS_OK;
1150 bool gfxUserFontSet::UserFontCache::Entry::KeyEquals(
1151 const KeyTypePointer aKey) const {
1152 const gfxFontEntry* fe = aKey->mFontEntry;
1154 if (!mURI->Equals(aKey->mURI)) {
1155 return false;
1158 // For data: URIs, we don't care about the principal; otherwise, check it.
1159 if (!IgnorePrincipal(mURI)) {
1160 NS_ASSERTION(mPrincipal && aKey->mPrincipal,
1161 "only data: URIs are allowed to omit the principal");
1162 if (!mPrincipal->Equals(aKey->mPrincipal)) {
1163 return false;
1167 if (mPrivate != aKey->mPrivate) {
1168 return false;
1171 if (mFontEntry->SlantStyle() != fe->SlantStyle() ||
1172 mFontEntry->Weight() != fe->Weight() ||
1173 mFontEntry->Stretch() != fe->Stretch() ||
1174 mFontEntry->mRangeFlags != fe->mRangeFlags ||
1175 mFontEntry->mFeatureSettings != fe->mFeatureSettings ||
1176 mFontEntry->mVariationSettings != fe->mVariationSettings ||
1177 mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
1178 mFontEntry->mAscentOverride != fe->mAscentOverride ||
1179 mFontEntry->mDescentOverride != fe->mDescentOverride ||
1180 mFontEntry->mLineGapOverride != fe->mLineGapOverride ||
1181 mFontEntry->mSizeAdjust != fe->mSizeAdjust ||
1182 mFontEntry->mFamilyName != fe->mFamilyName) {
1183 return false;
1186 return true;
1189 void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry) {
1190 NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0,
1191 "caching a font associated with no family yet");
1193 // if caching is disabled, simply return
1194 if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
1195 return;
1198 gfxUserFontData* data = aFontEntry->mUserFontData.get();
1199 if (data->mIsBuffer) {
1200 #ifdef DEBUG_USERFONT_CACHE
1201 printf("userfontcache skipped fontentry with buffer source: %p\n",
1202 aFontEntry);
1203 #endif
1204 return;
1207 if (!sUserFonts) {
1208 sUserFonts = new nsTHashtable<Entry>;
1210 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1211 if (obs) {
1212 Flusher* flusher = new Flusher;
1213 obs->AddObserver(flusher, "cacheservice:empty-cache", false);
1214 obs->AddObserver(flusher, "last-pb-context-exited", false);
1215 obs->AddObserver(flusher, "xpcom-shutdown", false);
1218 // Create and register a memory reporter for sUserFonts.
1219 // This reporter is never unregistered, but that's OK because
1220 // the reporter checks whether sUserFonts is null, so it would
1221 // be safe to call even after UserFontCache::Shutdown has deleted
1222 // the cache.
1223 RegisterStrongMemoryReporter(new MemoryReporter());
1226 // For data: URIs, the principal is ignored; anyone who has the same
1227 // data: URI is able to load it and get an equivalent font.
1228 // Otherwise, the principal is used as part of the cache key.
1229 gfxFontSrcPrincipal* principal;
1230 if (IgnorePrincipal(data->mURI)) {
1231 principal = nullptr;
1232 } else {
1233 principal = data->mPrincipal;
1235 sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate));
1237 #ifdef DEBUG_USERFONT_CACHE
1238 printf("userfontcache added fontentry: %p\n", aFontEntry);
1239 Dump();
1240 #endif
1243 void gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry* aFontEntry) {
1244 if (!sUserFonts) {
1245 // if we've already deleted the cache (i.e. during shutdown),
1246 // just ignore this
1247 return;
1250 // We can't simply use RemoveEntry here because it's possible the principal
1251 // may have changed since the font was cached, in which case the lookup
1252 // would no longer find the entry (bug 838105).
1253 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1254 if (i.Get()->GetFontEntry() == aFontEntry) {
1255 i.Remove();
1259 #ifdef DEBUG_USERFONT_CACHE
1260 printf("userfontcache removed fontentry: %p\n", aFontEntry);
1261 Dump();
1262 #endif
1265 gfxFontEntry* gfxUserFontSet::UserFontCache::GetFont(
1266 const gfxFontFaceSrc& aSrc, const gfxUserFontEntry& aUserFontEntry) {
1267 if (!sUserFonts ||
1268 Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
1269 return nullptr;
1272 RefPtr<gfxUserFontSet> srcFontSet = aUserFontEntry.GetUserFontSet();
1273 if (NS_WARN_IF(!srcFontSet) || srcFontSet->BypassCache()) {
1274 return nullptr;
1277 // Ignore principal when looking up a data: URI.
1278 RefPtr<gfxFontSrcPrincipal> principal =
1279 IgnorePrincipal(aSrc.mURI) ? nullptr : aSrc.LoadPrincipal(*srcFontSet);
1281 Entry* entry = sUserFonts->GetEntry(
1282 Key(aSrc.mURI, principal, const_cast<gfxUserFontEntry*>(&aUserFontEntry),
1283 srcFontSet->GetPrivateBrowsing()));
1284 if (!entry) {
1285 return nullptr;
1288 // We have to perform another content policy check here to prevent
1289 // cache poisoning. E.g. a.com loads a font into the cache but
1290 // b.com has a CSP not allowing any fonts to be loaded.
1291 if (!srcFontSet->IsFontLoadAllowed(aSrc)) {
1292 return nullptr;
1295 return entry->GetFontEntry();
1298 void gfxUserFontSet::UserFontCache::Shutdown() {
1299 if (sUserFonts) {
1300 delete sUserFonts;
1301 sUserFonts = nullptr;
1305 MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)
1307 void gfxUserFontSet::UserFontCache::Entry::ReportMemory(
1308 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1309 bool aAnonymize) {
1310 MOZ_ASSERT(mFontEntry);
1311 nsAutoCString path("explicit/gfx/user-fonts/font(");
1313 if (aAnonymize) {
1314 path.AppendPrintf("<anonymized-%p>", this);
1315 } else {
1316 path.AppendPrintf("family=%s", mFontEntry->mFamilyName.get());
1317 if (mURI) {
1318 nsCString spec = mURI->GetSpecOrDefault();
1319 spec.ReplaceChar('/', '\\');
1320 // Some fonts are loaded using horrendously-long data: URIs;
1321 // truncate those before reporting them.
1322 if (mURI->get()->SchemeIs("data") && spec.Length() > 255) {
1323 spec.Truncate(252);
1324 spec.AppendLiteral("...");
1326 path.AppendPrintf(", url=%s", spec.get());
1328 if (mPrincipal) {
1329 nsAutoCString spec;
1330 mPrincipal->NodePrincipal()->GetAsciiSpec(spec);
1331 if (!spec.IsEmpty()) {
1332 // Include a clue as to who loaded this resource. (Note
1333 // that because of font entry sharing, other pages may now
1334 // be using this resource, and the original page may not
1335 // even be loaded any longer.)
1336 spec.ReplaceChar('/', '\\');
1337 path.AppendPrintf(", principal=%s", spec.get());
1341 path.Append(')');
1343 aHandleReport->Callback(
1344 ""_ns, path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
1345 mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf),
1346 "Memory used by @font-face resource."_ns, aData);
1349 NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter,
1350 nsIMemoryReporter)
1352 NS_IMETHODIMP
1353 gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports(
1354 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1355 bool aAnonymize) {
1356 if (!sUserFonts) {
1357 return NS_OK;
1360 for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
1361 it.Get()->ReportMemory(aHandleReport, aData, aAnonymize);
1364 MOZ_COLLECT_REPORT(
1365 "explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES,
1366 sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf),
1367 "Memory used by the @font-face cache, not counting the actual font "
1368 "resources.");
1370 return NS_OK;
1373 #ifdef DEBUG_USERFONT_CACHE
1375 void gfxUserFontSet::UserFontCache::Entry::Dump() {
1376 nsresult rv;
1378 nsAutoCString principalURISpec("(null)");
1379 bool setDomain = false;
1381 if (mPrincipal) {
1382 nsCOMPtr<nsIURI> principalURI;
1383 rv = mPrincipal->NodePrincipal()->GetURI(getter_AddRefs(principalURI));
1384 if (NS_SUCCEEDED(rv)) {
1385 principalURI->GetSpec(principalURISpec);
1388 nsCOMPtr<nsIURI> domainURI;
1389 mPrincipal->NodePrincipal()->GetDomain(getter_AddRefs(domainURI));
1390 if (domainURI) {
1391 setDomain = true;
1395 NS_ASSERTION(mURI, "null URI in userfont cache entry");
1397 printf(
1398 "userfontcache fontEntry: %p fonturihash: %8.8x "
1399 "family: %s domainset: %s principal: [%s]\n",
1400 mFontEntry, mURI->Hash(), mFontEntry->FamilyName().get(),
1401 setDomain ? "true" : "false", principalURISpec.get());
1404 void gfxUserFontSet::UserFontCache::Dump() {
1405 if (!sUserFonts) {
1406 return;
1409 printf("userfontcache dump count: %d ========\n", sUserFonts->Count());
1410 for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
1411 it.Get()->Dump();
1413 printf("userfontcache dump ==================\n");
1416 #endif
1418 #undef LOG
1419 #undef LOG_ENABLED