1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/. */
5
6 #include "mozilla/Logging.h"
7
8 #include "gfxUserFontSet.h"
9 #include "gfxPlatform.h"
10 #include "gfxPrefs.h"
11 #include "nsIProtocolHandler.h"
12 #include "gfxFontConstants.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/Services.h"
15 #include "mozilla/Telemetry.h"
16 #include "mozilla/gfx/2D.h"
17 #include "gfxPlatformFontList.h"
18 #include "mozilla/ServoStyleSet.h"
19 #include "mozilla/PostTraversalTask.h"
20
21 #include "opentype-sanitiser.h"
22 #include "ots-memory-stream.h"
23
24 using namespace mozilla;
25
GetUserFontsLog()26 mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() {
27 static LazyLogModule sLog("userfonts");
28 return sLog;
29 }
30
31 #define LOG(args) \
32 MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
33 #define LOG_ENABLED() \
34 MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug)
35
36 static uint64_t sFontSetGeneration = 0;
37
38 // Based on ots::ExpandingMemoryStream from ots-memory-stream.h,
39 // adapted to use Mozilla allocators and to allow the final
40 // memory buffer to be adopted by the client.
41 class ExpandingMemoryStream : public ots::OTSStream {
42 public:
ExpandingMemoryStream(size_t initial,size_t limit)43 ExpandingMemoryStream(size_t initial, size_t limit)
44 : mLength(initial), mLimit(limit), mOff(0) {
45 mPtr = moz_xmalloc(mLength);
46 }
47
~ExpandingMemoryStream()48 ~ExpandingMemoryStream() { free(mPtr); }
49
50 // Return the buffer, resized to fit its contents (as it may have been
51 // over-allocated during growth), and give up ownership of it so the
52 // caller becomes responsible to call free() when finished with it.
forget()53 void* forget() {
54 void* p = moz_xrealloc(mPtr, mOff);
55 mPtr = nullptr;
56 return p;
57 }
58
WriteRaw(const void * data,size_t length)59 bool WriteRaw(const void* data, size_t length) override {
60 if ((mOff + length > mLength) ||
61 (mLength > std::numeric_limits<size_t>::max() - mOff)) {
62 if (mLength == mLimit) {
63 return false;
64 }
65 size_t newLength = (mLength + 1) * 2;
66 if (newLength < mLength) {
67 return false;
68 }
69 if (newLength > mLimit) {
70 newLength = mLimit;
71 }
72 mPtr = moz_xrealloc(mPtr, newLength);
73 mLength = newLength;
74 return WriteRaw(data, length);
75 }
76 std::memcpy(static_cast<char*>(mPtr) + mOff, data, length);
77 mOff += length;
78 return true;
79 }
80
Seek(off_t position)81 bool Seek(off_t position) override {
82 if (position < 0) {
83 return false;
84 }
85 if (static_cast<size_t>(position) > mLength) {
86 return false;
87 }
88 mOff = position;
89 return true;
90 }
91
Tell() const92 off_t Tell() const override { return mOff; }
93
94 private:
95 void* mPtr;
96 size_t mLength;
97 const size_t mLimit;
98 off_t mOff;
99 };
100
gfxUserFontEntry(gfxUserFontSet * aFontSet,const nsTArray<gfxFontFaceSrc> & aFontFaceSrcList,uint32_t aWeight,int32_t aStretch,uint8_t aStyle,const nsTArray<gfxFontFeature> & aFeatureSettings,const nsTArray<gfxFontVariation> & aVariationSettings,uint32_t aLanguageOverride,gfxCharacterMap * aUnicodeRanges,uint8_t aFontDisplay)101 gfxUserFontEntry::gfxUserFontEntry(
102 gfxUserFontSet* aFontSet, const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
103 uint32_t aWeight, int32_t aStretch, uint8_t aStyle,
104 const nsTArray<gfxFontFeature>& aFeatureSettings,
105 const nsTArray<gfxFontVariation>& aVariationSettings,
106 uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges,
107 uint8_t aFontDisplay)
108 : gfxFontEntry(NS_LITERAL_STRING("userfont")),
109 mUserFontLoadState(STATUS_NOT_LOADED),
110 mFontDataLoadingState(NOT_LOADING),
111 mUnsupportedFormat(false),
112 mFontDisplay(aFontDisplay),
113 mLoader(nullptr),
114 mFontSet(aFontSet) {
115 MOZ_ASSERT(aWeight != 0,
116 "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
117 mIsUserFontContainer = true;
118 mSrcList = aFontFaceSrcList;
119 mSrcIndex = 0;
120 mWeight = aWeight;
121 mStretch = aStretch;
122 mStyle = aStyle;
123 mFeatureSettings.AppendElements(aFeatureSettings);
124 mVariationSettings.AppendElements(aVariationSettings);
125 mLanguageOverride = aLanguageOverride;
126 mCharacterMap = aUnicodeRanges;
127 }
128
~gfxUserFontEntry()129 gfxUserFontEntry::~gfxUserFontEntry() {
130 // Assert that we don't drop any gfxUserFontEntry objects during a Servo
131 // traversal, since PostTraversalTask objects can hold raw pointers to
132 // gfxUserFontEntry objects.
133 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
134 }
135
Matches(const nsTArray<gfxFontFaceSrc> & aFontFaceSrcList,uint32_t aWeight,int32_t aStretch,uint8_t aStyle,const nsTArray<gfxFontFeature> & aFeatureSettings,const nsTArray<gfxFontVariation> & aVariationSettings,uint32_t aLanguageOverride,gfxCharacterMap * aUnicodeRanges,uint8_t aFontDisplay)136 bool gfxUserFontEntry::Matches(
137 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, uint32_t aWeight,
138 int32_t aStretch, uint8_t aStyle,
139 const nsTArray<gfxFontFeature>& aFeatureSettings,
140 const nsTArray<gfxFontVariation>& aVariationSettings,
141 uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges,
142 uint8_t aFontDisplay) {
143 return mWeight == aWeight && mStretch == aStretch && mStyle == aStyle &&
144 mFeatureSettings == aFeatureSettings &&
145 mVariationSettings == aVariationSettings &&
146 mLanguageOverride == aLanguageOverride &&
147 mSrcList == aFontFaceSrcList && mFontDisplay == aFontDisplay &&
148 ((!aUnicodeRanges && !mCharacterMap) ||
149 (aUnicodeRanges && mCharacterMap &&
150 mCharacterMap->Equals(aUnicodeRanges)));
151 }
152
CreateFontInstance(const gfxFontStyle * aFontStyle,bool aNeedsBold)153 gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle,
154 bool aNeedsBold) {
155 NS_NOTREACHED(
156 "should only be creating a gfxFont"
157 " with an actual platform font entry");
158
159 // userfont entry is a container, can't create font from the container
160 return nullptr;
161 }
162
163 class MOZ_STACK_CLASS gfxOTSContext : public ots::OTSContext {
164 public:
gfxOTSContext(gfxUserFontEntry * aUserFontEntry)165 explicit gfxOTSContext(gfxUserFontEntry* aUserFontEntry)
166 : mUserFontEntry(aUserFontEntry) {
167 // Whether to apply OTS validation to OpenType Layout tables
168 mCheckOTLTables = gfxPrefs::ValidateOTLTables();
169 // Whether to preserve Variation tables in downloaded fonts
170 mKeepVariationTables = gfxPrefs::KeepVariationTables();
171 // Whether to preserve color bitmap glyphs
172 mKeepColorBitmaps = gfxPrefs::KeepColorBitmaps();
173 }
174
GetTableAction(uint32_t aTag)175 virtual ots::TableAction GetTableAction(uint32_t aTag) override {
176 // Preserve Graphite, color glyph and SVG tables,
177 // and possibly OTL and Variation tables (depending on prefs)
178 if ((!mCheckOTLTables && (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') ||
179 aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') ||
180 aTag == TRUETYPE_TAG('G', 'S', 'U', 'B'))) ||
181 (mKeepVariationTables && (aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') ||
182 aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') ||
183 aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') ||
184 aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') ||
185 aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') ||
186 aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') ||
187 aTag == TRUETYPE_TAG('S', 'T', 'A', 'T') ||
188 aTag == TRUETYPE_TAG('V', 'V', 'A', 'R'))) ||
189 aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
190 aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
191 aTag == TRUETYPE_TAG('C', 'P', 'A', 'L') ||
192 (mKeepColorBitmaps && (aTag == TRUETYPE_TAG('C', 'B', 'D', 'T') ||
193 aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) ||
194 false) {
195 return ots::TABLE_ACTION_PASSTHRU;
196 }
197 return ots::TABLE_ACTION_DEFAULT;
198 }
199
Message(int level,const char * format,...)200 virtual void Message(int level, const char* format,
201 ...) MSGFUNC_FMT_ATTR override {
202 va_list va;
203 va_start(va, format);
204
205 nsCString msg;
206 msg.AppendPrintf(format, va);
207
208 va_end(va);
209
210 if (level > 0) {
211 // For warnings (rather than errors that cause the font to fail),
212 // we only report the first instance of any given message.
213 if (mWarningsIssued.Contains(msg)) {
214 return;
215 }
216 mWarningsIssued.PutEntry(msg);
217 }
218
219 mUserFontEntry->mFontSet->LogMessage(mUserFontEntry, msg.get());
220 }
221
222 private:
223 gfxUserFontEntry* mUserFontEntry;
224 nsTHashtable<nsCStringHashKey> mWarningsIssued;
225 bool mCheckOTLTables;
226 bool mKeepVariationTables;
227 bool mKeepColorBitmaps;
228 };
229
230 // Call the OTS library to sanitize an sfnt before attempting to use it.
231 // Returns a newly-allocated block, or nullptr in case of fatal errors.
SanitizeOpenTypeData(const uint8_t * aData,uint32_t aLength,uint32_t & aSaneLength,gfxUserFontType aFontType)232 const uint8_t* gfxUserFontEntry::SanitizeOpenTypeData(
233 const uint8_t* aData, uint32_t aLength, uint32_t& aSaneLength,
234 gfxUserFontType aFontType) {
235 if (aFontType == GFX_USERFONT_UNKNOWN) {
236 aSaneLength = 0;
237 return nullptr;
238 }
239
240 uint32_t lengthHint = aLength;
241 if (aFontType == GFX_USERFONT_WOFF) {
242 lengthHint *= 2;
243 } else if (aFontType == GFX_USERFONT_WOFF2) {
244 lengthHint *= 3;
245 }
246
247 // limit output/expansion to 256MB
248 ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256);
249
250 gfxOTSContext otsContext(this);
251 if (!otsContext.Process(&output, aData, aLength)) {
252 // Failed to decode/sanitize the font, so discard it.
253 aSaneLength = 0;
254 return nullptr;
255 }
256
257 aSaneLength = output.Tell();
258 return static_cast<const uint8_t*>(output.forget());
259 }
260
StoreUserFontData(gfxFontEntry * aFontEntry,bool aPrivate,const nsAString & aOriginalName,FallibleTArray<uint8_t> * aMetadata,uint32_t aMetaOrigLen,uint8_t aCompression)261 void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
262 bool aPrivate,
263 const nsAString& aOriginalName,
264 FallibleTArray<uint8_t>* aMetadata,
265 uint32_t aMetaOrigLen,
266 uint8_t aCompression) {
267 if (!aFontEntry->mUserFontData) {
268 aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>();
269 }
270 gfxUserFontData* userFontData = aFontEntry->mUserFontData.get();
271 userFontData->mSrcIndex = mSrcIndex;
272 const gfxFontFaceSrc& src = mSrcList[mSrcIndex];
273 switch (src.mSourceType) {
274 case gfxFontFaceSrc::eSourceType_Local:
275 userFontData->mLocalName = src.mLocalName;
276 break;
277 case gfxFontFaceSrc::eSourceType_URL:
278 userFontData->mURI = src.mURI;
279 userFontData->mPrincipal = mPrincipal;
280 break;
281 case gfxFontFaceSrc::eSourceType_Buffer:
282 userFontData->mIsBuffer = true;
283 break;
284 }
285 userFontData->mPrivate = aPrivate;
286 userFontData->mFormat = src.mFormatFlags;
287 userFontData->mRealName = aOriginalName;
288 if (aMetadata) {
289 userFontData->mMetadata.SwapElements(*aMetadata);
290 userFontData->mMetaOrigLen = aMetaOrigLen;
291 userFontData->mCompression = aCompression;
292 }
293 }
294
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const295 size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
296 return aMallocSizeOf(this) +
297 mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) +
298 mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
299 mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
300 // Not counting mURI and mPrincipal, as those will be shared.
301 }
302
303 /*virtual*/
~gfxUserFontFamily()304 gfxUserFontFamily::~gfxUserFontFamily() {
305 // Should not be dropped by stylo
306 MOZ_ASSERT(NS_IsMainThread());
307 }
308
GetFamilyNameAndURIForLogging(nsACString & aFamilyName,nsACString & aURI)309 void gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName,
310 nsACString& aURI) {
311 aFamilyName.Assign(NS_ConvertUTF16toUTF8(mFamilyName));
312
313 aURI.Truncate();
314 if (mSrcIndex == mSrcList.Length()) {
315 aURI.AppendLiteral("(end of source list)");
316 } else {
317 if (mSrcList[mSrcIndex].mURI) {
318 mSrcList[mSrcIndex].mURI->GetSpec(aURI);
319 // If the source URI was very long, elide the middle of it.
320 // In principle, the byte-oriented chopping here could leave us
321 // with partial UTF-8 characters at the point where we cut it,
322 // but it really doesn't matter as this is just for logging.
323 const uint32_t kMaxURILengthForLogging = 256;
324 // UTF-8 ellipsis, with spaces to allow additional wrap opportunities
325 // in the resulting log message
326 const char kEllipsis[] = {' ', '\xE2', '\x80', '\xA6', ' '};
327 if (aURI.Length() > kMaxURILengthForLogging) {
328 aURI.Replace(kMaxURILengthForLogging / 2,
329 aURI.Length() - kMaxURILengthForLogging, kEllipsis,
330 ArrayLength(kEllipsis));
331 }
332 } else {
333 aURI.AppendLiteral("(invalid URI)");
334 }
335 }
336 }
337
338 struct WOFFHeader {
339 AutoSwap_PRUint32 signature;
340 AutoSwap_PRUint32 flavor;
341 AutoSwap_PRUint32 length;
342 AutoSwap_PRUint16 numTables;
343 AutoSwap_PRUint16 reserved;
344 AutoSwap_PRUint32 totalSfntSize;
345 AutoSwap_PRUint16 majorVersion;
346 AutoSwap_PRUint16 minorVersion;
347 AutoSwap_PRUint32 metaOffset;
348 AutoSwap_PRUint32 metaCompLen;
349 AutoSwap_PRUint32 metaOrigLen;
350 AutoSwap_PRUint32 privOffset;
351 AutoSwap_PRUint32 privLen;
352 };
353
354 struct WOFF2Header {
355 AutoSwap_PRUint32 signature;
356 AutoSwap_PRUint32 flavor;
357 AutoSwap_PRUint32 length;
358 AutoSwap_PRUint16 numTables;
359 AutoSwap_PRUint16 reserved;
360 AutoSwap_PRUint32 totalSfntSize;
361 AutoSwap_PRUint32 totalCompressedSize;
362 AutoSwap_PRUint16 majorVersion;
363 AutoSwap_PRUint16 minorVersion;
364 AutoSwap_PRUint32 metaOffset;
365 AutoSwap_PRUint32 metaCompLen;
366 AutoSwap_PRUint32 metaOrigLen;
367 AutoSwap_PRUint32 privOffset;
368 AutoSwap_PRUint32 privLen;
369 };
370
371 template <typename HeaderT>
CopyWOFFMetadata(const uint8_t * aFontData,uint32_t aLength,FallibleTArray<uint8_t> * aMetadata,uint32_t * aMetaOrigLen)372 void CopyWOFFMetadata(const uint8_t* aFontData, uint32_t aLength,
373 FallibleTArray<uint8_t>* aMetadata,
374 uint32_t* aMetaOrigLen) {
375 // This function may be called with arbitrary, unvalidated "font" data
376 // from @font-face, so it needs to be careful to bounds-check, etc.,
377 // before trying to read anything.
378 // This just saves a copy of the compressed data block; it does NOT check
379 // that the block can be successfully decompressed, or that it contains
380 // well-formed/valid XML metadata.
381 if (aLength < sizeof(HeaderT)) {
382 return;
383 }
384 const HeaderT* woff = reinterpret_cast<const HeaderT*>(aFontData);
385 uint32_t metaOffset = woff->metaOffset;
386 uint32_t metaCompLen = woff->metaCompLen;
387 if (!metaOffset || !metaCompLen || !woff->metaOrigLen) {
388 return;
389 }
390 if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) {
391 return;
392 }
393 if (!aMetadata->SetLength(woff->metaCompLen, fallible)) {
394 return;
395 }
396 memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen);
397 *aMetaOrigLen = woff->metaOrigLen;
398 }
399
LoadNextSrc()400 void gfxUserFontEntry::LoadNextSrc() {
401 NS_ASSERTION(mSrcIndex < mSrcList.Length(),
402 "already at the end of the src list for user font");
403 NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
404 mUserFontLoadState == STATUS_LOAD_PENDING ||
405 mUserFontLoadState == STATUS_LOADING) &&
406 mFontDataLoadingState < LOADING_FAILED,
407 "attempting to load a font that has either completed or failed");
408
409 if (mUserFontLoadState == STATUS_NOT_LOADED) {
410 SetLoadState(STATUS_LOADING);
411 mFontDataLoadingState = LOADING_STARTED;
412 mUnsupportedFormat = false;
413 } else {
414 // we were already loading; move to the next source,
415 // but don't reset state - if we've already timed out,
416 // that counts against the new download
417 mSrcIndex++;
418 }
419
420 DoLoadNextSrc(false);
421 }
422
ContinueLoad()423 void gfxUserFontEntry::ContinueLoad() {
424 MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING);
425 MOZ_ASSERT(mSrcList[mSrcIndex].mSourceType ==
426 gfxFontFaceSrc::eSourceType_URL);
427
428 SetLoadState(STATUS_LOADING);
429 DoLoadNextSrc(true);
430 if (LoadState() != STATUS_LOADING) {
431 MOZ_ASSERT(mUserFontLoadState != STATUS_LOAD_PENDING,
432 "Not in parallel traversal, shouldn't get LOAD_PENDING again");
433 // Loading is synchronously finished (loaded from cache or failed). We
434 // need to increment the generation so that we flush the style data to
435 // use the new loaded font face.
436 // Without parallel traversal, we would simply get the right font data
437 // after the first call to DoLoadNextSrc() in this case, so we don't need
438 // to touch the generation to trigger another restyle.
439 // XXX We may want to return synchronously in parallel traversal in those
440 // cases as well if possible, so that we don't have an additional restyle.
441 // That doesn't work currently because nsIDocument::GetDocShell (called
442 // from FontFaceSet::CheckFontLoad) dereferences a weak pointer, which is
443 // not allowed in parallel traversal.
444 IncrementGeneration();
445 }
446 }
447
IgnorePrincipal(gfxFontSrcURI * aURI)448 static bool IgnorePrincipal(gfxFontSrcURI* aURI) {
449 return aURI->InheritsSecurityContext();
450 }
451
DoLoadNextSrc(bool aForceAsync)452 void gfxUserFontEntry::DoLoadNextSrc(bool aForceAsync) {
453 uint32_t numSrc = mSrcList.Length();
454
455 // load each src entry in turn, until a local face is found
456 // or a download begins successfully
457 while (mSrcIndex < numSrc) {
458 gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex];
459
460 // src local ==> lookup and load immediately
461
462 if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) {
463 // Don't look up local fonts if the font whitelist is being used.
464 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
465 gfxFontEntry* fe =
466 pfl && pfl->IsFontFamilyWhitelistActive()
467 ? nullptr
468 : gfxPlatform::GetPlatform()->LookupLocalFont(
469 currSrc.mLocalName, mWeight, mStretch, mStyle);
470 nsTArray<gfxUserFontSet*> fontSets;
471 GetUserFontSets(fontSets);
472 for (gfxUserFontSet* fontSet : fontSets) {
473 // We need to note on each gfxUserFontSet that contains the user
474 // font entry that we used a local() rule.
475 fontSet->SetLocalRulesUsed();
476 }
477 if (fe) {
478 LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n",
479 mFontSet, mSrcIndex,
480 NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
481 NS_ConvertUTF16toUTF8(mFamilyName).get(),
482 uint32_t(mFontSet->mGeneration)));
483 fe->mFeatureSettings.AppendElements(mFeatureSettings);
484 fe->mVariationSettings.AppendElements(mVariationSettings);
485 fe->mLanguageOverride = mLanguageOverride;
486 fe->mFamilyName = mFamilyName;
487 // For src:local(), we don't care whether the request is from
488 // a private window as there's no issue of caching resources;
489 // local fonts are just available all the time.
490 StoreUserFontData(fe, false, nsString(), nullptr, 0,
491 gfxUserFontData::kUnknownCompression);
492 mPlatformFontEntry = fe;
493 SetLoadState(STATUS_LOADED);
494 Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
495 currSrc.mSourceType + 1);
496 return;
497 } else {
498 LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", mFontSet,
499 mSrcIndex, NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
500 NS_ConvertUTF16toUTF8(mFamilyName).get()));
501 }
502 }
503
504 // src url ==> start the load process
505 else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) {
506 if (gfxPlatform::GetPlatform()->IsFontFormatSupported(
507 currSrc.mFormatFlags)) {
508 if (ServoStyleSet* set = ServoStyleSet::Current()) {
509 // Only support style worker threads synchronously getting
510 // entries from the font cache when it's not a data: URI
511 // @font-face that came from UA or user sheets, since we
512 // were not able to call IsFontLoadAllowed ahead of time
513 // for these entries.
514 if (currSrc.mUseOriginPrincipal && IgnorePrincipal(currSrc.mURI)) {
515 set->AppendTask(PostTraversalTask::LoadFontEntry(this));
516 SetLoadState(STATUS_LOAD_PENDING);
517 return;
518 }
519 }
520
521 gfxFontSrcPrincipal* principal = nullptr;
522 bool bypassCache;
523 nsresult rv =
524 mFontSet->CheckFontLoad(&currSrc, &principal, &bypassCache);
525
526 if (NS_SUCCEEDED(rv) && principal != nullptr) {
527 if (!bypassCache) {
528 // see if we have an existing entry for this source
529 gfxFontEntry* fe = gfxUserFontSet::UserFontCache::GetFont(
530 currSrc.mURI, principal, this, mFontSet->GetPrivateBrowsing());
531 if (fe) {
532 mPlatformFontEntry = fe;
533 SetLoadState(STATUS_LOADED);
534 if (LOG_ENABLED()) {
535 LOG((
536 "userfonts (%p) [src %d] "
537 "loaded uri from cache: (%s) for (%s)\n",
538 mFontSet, mSrcIndex, currSrc.mURI->GetSpecOrDefault().get(),
539 NS_ConvertUTF16toUTF8(mFamilyName).get()));
540 }
541 return;
542 }
543 }
544
545 if (ServoStyleSet* set = ServoStyleSet::Current()) {
546 // If we need to start a font load and we're on a style
547 // worker thread, we have to defer it.
548 set->AppendTask(PostTraversalTask::LoadFontEntry(this));
549 SetLoadState(STATUS_LOAD_PENDING);
550 return;
551 }
552
553 // record the principal returned by CheckFontLoad,
554 // for use when creating a channel
555 // and when caching the loaded entry
556 mPrincipal = principal;
557
558 bool loadDoesntSpin = false;
559 if (!aForceAsync) {
560 loadDoesntSpin = currSrc.mURI->SyncLoadIsOK();
561 }
562
563 if (NS_SUCCEEDED(rv) && loadDoesntSpin) {
564 uint8_t* buffer = nullptr;
565 uint32_t bufferLength = 0;
566
567 // sync load font immediately
568 rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer,
569 bufferLength);
570
571 if (NS_SUCCEEDED(rv) && LoadPlatformFont(buffer, bufferLength)) {
572 SetLoadState(STATUS_LOADED);
573 Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
574 currSrc.mSourceType + 1);
575 return;
576 } else {
577 mFontSet->LogMessage(this, "font load failed",
578 nsIScriptError::errorFlag, rv);
579 }
580
581 } else {
582 // otherwise load font async
583 rv = mFontSet->StartLoad(this, &currSrc);
584 bool loadOK = NS_SUCCEEDED(rv);
585
586 if (loadOK) {
587 if (LOG_ENABLED()) {
588 LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n",
589 mFontSet, mSrcIndex,
590 currSrc.mURI->GetSpecOrDefault().get(),
591 NS_ConvertUTF16toUTF8(mFamilyName).get()));
592 }
593 return;
594 } else {
595 mFontSet->LogMessage(this, "download failed",
596 nsIScriptError::errorFlag, rv);
597 }
598 }
599 } else {
600 mFontSet->LogMessage(this, "download not allowed",
601 nsIScriptError::errorFlag, rv);
602 }
603 } else {
604 // We don't log a warning to the web console yet,
605 // as another source may load successfully
606 mUnsupportedFormat = true;
607 }
608 }
609
610 // FontFace buffer ==> load immediately
611
612 else {
613 MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer);
614
615 uint8_t* buffer = nullptr;
616 uint32_t bufferLength = 0;
617
618 // sync load font immediately
619 currSrc.mBuffer->TakeBuffer(buffer, bufferLength);
620 if (buffer && LoadPlatformFont(buffer, bufferLength)) {
621 // LoadPlatformFont takes ownership of the buffer, so no need
622 // to free it here.
623 SetLoadState(STATUS_LOADED);
624 Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
625 currSrc.mSourceType + 1);
626 return;
627 } else {
628 mFontSet->LogMessage(this, "font load failed",
629 nsIScriptError::errorFlag);
630 }
631 }
632
633 mSrcIndex++;
634 }
635
636 if (mUnsupportedFormat) {
637 mFontSet->LogMessage(this, "no supported format found",
638 nsIScriptError::warningFlag);
639 }
640
641 // all src's failed; mark this entry as unusable (so fallback will occur)
642 LOG(("userfonts (%p) failed all src for (%s)\n", mFontSet,
643 NS_ConvertUTF16toUTF8(mFamilyName).get()));
644 mFontDataLoadingState = LOADING_FAILED;
645 SetLoadState(STATUS_FAILED);
646 }
647
SetLoadState(UserFontLoadState aLoadState)648 void gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState) {
649 mUserFontLoadState = aLoadState;
650 }
651
MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)652 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)
653
654 bool gfxUserFontEntry::LoadPlatformFont(const uint8_t* aFontData,
655 uint32_t& aLength) {
656 NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
657 mUserFontLoadState == STATUS_LOAD_PENDING ||
658 mUserFontLoadState == STATUS_LOADING) &&
659 mFontDataLoadingState < LOADING_FAILED,
660 "attempting to load a font that has either completed or failed");
661
662 gfxFontEntry* fe = nullptr;
663
664 gfxUserFontType fontType =
665 gfxFontUtils::DetermineFontDataType(aFontData, aLength);
666 Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(fontType));
667
668 // Unwrap/decompress/sanitize or otherwise munge the downloaded data
669 // to make a usable sfnt structure.
670
671 // Because platform font activation code may replace the name table
672 // in the font with a synthetic one, we save the original name so that
673 // it can be reported via the InspectorUtils API.
674 nsAutoString originalFullName;
675
676 // Call the OTS sanitizer; this will also decode WOFF to sfnt
677 // if necessary. The original data in aFontData is left unchanged.
678 uint32_t saneLen;
679 uint32_t fontCompressionRatio = 0;
680 size_t computedSize = 0;
681 const uint8_t* saneData =
682 SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType);
683 if (!saneData) {
684 mFontSet->LogMessage(this, "rejected by sanitizer");
685 } else {
686 // Check whether saneData is a known OpenType format; it might be
687 // a TrueType Collection, which OTS would accept but we don't yet
688 // know how to handle. If so, discard.
689 if (gfxFontUtils::DetermineFontDataType(saneData, saneLen) !=
690 GFX_USERFONT_OPENTYPE) {
691 mFontSet->LogMessage(this, "not a supported OpenType format");
692 free((void*)saneData);
693 saneData = nullptr;
694 }
695 }
696 if (saneData) {
697 if (saneLen) {
698 fontCompressionRatio = uint32_t(100.0 * aLength / saneLen + 0.5);
699 if (fontType == GFX_USERFONT_WOFF || fontType == GFX_USERFONT_WOFF2) {
700 Telemetry::Accumulate(fontType == GFX_USERFONT_WOFF
701 ? Telemetry::WEBFONT_COMPRESSION_WOFF
702 : Telemetry::WEBFONT_COMPRESSION_WOFF2,
703 fontCompressionRatio);
704 }
705 }
706
707 // The sanitizer ensures that we have a valid sfnt and a usable
708 // name table, so this should never fail unless we're out of
709 // memory, and GetFullNameFromSFNT is not directly exposed to
710 // arbitrary/malicious data from the web.
711 gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen, originalFullName);
712
713 // Record size for memory reporting purposes. We measure this now
714 // because by the time we potentially want to collect reports, this
715 // data block may have been handed off to opaque OS font APIs that
716 // don't allow us to retrieve or measure it directly.
717 // The *OnAlloc function will also tell DMD about this block, as the
718 // OS font code may hold on to it for an extended period.
719 computedSize = UserFontMallocSizeOfOnAlloc(saneData);
720
721 // Here ownership of saneData is passed to the platform,
722 // which will delete it when no longer required
723 fe = gfxPlatform::GetPlatform()->MakePlatformFont(
724 mName, mWeight, mStretch, mStyle, saneData, saneLen);
725 if (!fe) {
726 mFontSet->LogMessage(this, "not usable by platform");
727 }
728 }
729
730 if (fe) {
731 fe->mComputedSizeOfUserFont = computedSize;
732
733 // Save a copy of the metadata block (if present) for InspectorUtils
734 // to use if required. Ownership of the metadata block will be passed
735 // to the gfxUserFontData record below.
736 FallibleTArray<uint8_t> metadata;
737 uint32_t metaOrigLen = 0;
738 uint8_t compression = gfxUserFontData::kUnknownCompression;
739 if (fontType == GFX_USERFONT_WOFF) {
740 CopyWOFFMetadata<WOFFHeader>(aFontData, aLength, &metadata, &metaOrigLen);
741 compression = gfxUserFontData::kZlibCompression;
742 } else if (fontType == GFX_USERFONT_WOFF2) {
743 CopyWOFFMetadata<WOFF2Header>(aFontData, aLength, &metadata,
744 &metaOrigLen);
745 compression = gfxUserFontData::kBrotliCompression;
746 }
747
748 // copy OpenType feature/language settings from the userfont entry to the
749 // newly-created font entry
750 fe->mFeatureSettings.AppendElements(mFeatureSettings);
751 fe->mVariationSettings.AppendElements(mVariationSettings);
752 fe->mLanguageOverride = mLanguageOverride;
753 fe->mFamilyName = mFamilyName;
754 StoreUserFontData(fe, mFontSet->GetPrivateBrowsing(), originalFullName,
755 &metadata, metaOrigLen, compression);
756 if (LOG_ENABLED()) {
757 LOG(
758 ("userfonts (%p) [src %d] loaded uri: (%s) for (%s) "
759 "(%p) gen: %8.8x compress: %d%%\n",
760 mFontSet, mSrcIndex,
761 mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(),
762 NS_ConvertUTF16toUTF8(mFamilyName).get(), this,
763 uint32_t(mFontSet->mGeneration), fontCompressionRatio));
764 }
765 mPlatformFontEntry = fe;
766 SetLoadState(STATUS_LOADED);
767 gfxUserFontSet::UserFontCache::CacheFont(fe);
768 } else {
769 if (LOG_ENABLED()) {
770 LOG(
771 ("userfonts (%p) [src %d] failed uri: (%s) for (%s)"
772 " error making platform font\n",
773 mFontSet, mSrcIndex,
774 mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(),
775 NS_ConvertUTF16toUTF8(mFamilyName).get()));
776 }
777 }
778
779 // The downloaded data can now be discarded; the font entry is using the
780 // sanitized copy
781 free((void*)aFontData);
782
783 return fe != nullptr;
784 }
785
Load()786 void gfxUserFontEntry::Load() {
787 if (mUserFontLoadState == STATUS_NOT_LOADED) {
788 LoadNextSrc();
789 }
790 }
791
IncrementGeneration()792 void gfxUserFontEntry::IncrementGeneration() {
793 nsTArray<gfxUserFontSet*> fontSets;
794 GetUserFontSets(fontSets);
795 for (gfxUserFontSet* fontSet : fontSets) {
796 fontSet->IncrementGeneration();
797 }
798 }
799
800 // This is called when a font download finishes.
801 // Ownership of aFontData passes in here, and the font set must
802 // ensure that it is eventually deleted via free().
FontDataDownloadComplete(const uint8_t * aFontData,uint32_t aLength,nsresult aDownloadStatus)803 bool gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData,
804 uint32_t aLength,
805 nsresult aDownloadStatus) {
806 // forget about the loader, as we no longer potentially need to cancel it
807 // if the entry is obsoleted
808 mLoader = nullptr;
809
810 // download successful, make platform font using font data
811 if (NS_SUCCEEDED(aDownloadStatus) &&
812 mFontDataLoadingState != LOADING_TIMED_OUT) {
813 bool loaded = LoadPlatformFont(aFontData, aLength);
814 aFontData = nullptr;
815
816 if (loaded) {
817 IncrementGeneration();
818 return true;
819 }
820
821 } else {
822 // download failed
823 mFontSet->LogMessage(
824 this,
825 (mFontDataLoadingState != LOADING_TIMED_OUT ? "download failed"
826 : "download timed out"),
827 nsIScriptError::errorFlag, aDownloadStatus);
828 }
829
830 if (aFontData) {
831 free((void*)aFontData);
832 }
833
834 // error occurred, load next src if load not yet timed out
835 if (mFontDataLoadingState != LOADING_TIMED_OUT) {
836 LoadNextSrc();
837 }
838
839 // We ignore the status returned by LoadNext();
840 // even if loading failed, we need to bump the font-set generation
841 // and return true in order to trigger reflow, so that fallback
842 // will be used where the text was "masked" by the pending download
843 IncrementGeneration();
844 return true;
845 }
846
GetUserFontSets(nsTArray<gfxUserFontSet * > & aResult)847 void gfxUserFontEntry::GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult) {
848 aResult.Clear();
849 aResult.AppendElement(mFontSet);
850 }
851
gfxUserFontSet()852 gfxUserFontSet::gfxUserFontSet()
853 : mFontFamilies(4),
854 mLocalRulesUsed(false),
855 mRebuildLocalRules(false),
856 mDownloadCount(0),
857 mDownloadSize(0) {
858 IncrementGeneration(true);
859 gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
860 if (fp) {
861 fp->AddUserFontSet(this);
862 }
863 }
864
~gfxUserFontSet()865 gfxUserFontSet::~gfxUserFontSet() {
866 gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
867 if (fp) {
868 fp->RemoveUserFontSet(this);
869 }
870
871 UserFontCache::ClearAllowedFontSets(this);
872 }
873
FindOrCreateUserFontEntry(const nsAString & aFamilyName,const nsTArray<gfxFontFaceSrc> & aFontFaceSrcList,uint32_t aWeight,int32_t aStretch,uint8_t aStyle,const nsTArray<gfxFontFeature> & aFeatureSettings,const nsTArray<gfxFontVariation> & aVariationSettings,uint32_t aLanguageOverride,gfxCharacterMap * aUnicodeRanges,uint8_t aFontDisplay)874 already_AddRefed<gfxUserFontEntry> gfxUserFontSet::FindOrCreateUserFontEntry(
875 const nsAString& aFamilyName,
876 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, uint32_t aWeight,
877 int32_t aStretch, uint8_t aStyle,
878 const nsTArray<gfxFontFeature>& aFeatureSettings,
879 const nsTArray<gfxFontVariation>& aVariationSettings,
880 uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges,
881 uint8_t aFontDisplay) {
882 RefPtr<gfxUserFontEntry> entry;
883
884 // If there's already a userfont entry in the family whose descriptors all
885 // match, we can just move it to the end of the list instead of adding a new
886 // face that will always "shadow" the old one.
887 // Note that we can't do this for platform font entries, even if the
888 // style descriptors match, as they might have had a different source list,
889 // but we no longer have the old source list available to check.
890 gfxUserFontFamily* family = LookupFamily(aFamilyName);
891 if (family) {
892 entry = FindExistingUserFontEntry(
893 family, aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings,
894 aVariationSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay);
895 }
896
897 if (!entry) {
898 entry = CreateUserFontEntry(
899 aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings,
900 aVariationSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay);
901 entry->mFamilyName = aFamilyName;
902 }
903
904 return entry.forget();
905 }
906
CreateUserFontEntry(const nsTArray<gfxFontFaceSrc> & aFontFaceSrcList,uint32_t aWeight,int32_t aStretch,uint8_t aStyle,const nsTArray<gfxFontFeature> & aFeatureSettings,const nsTArray<gfxFontVariation> & aVariationSettings,uint32_t aLanguageOverride,gfxCharacterMap * aUnicodeRanges,uint8_t aFontDisplay)907 already_AddRefed<gfxUserFontEntry> gfxUserFontSet::CreateUserFontEntry(
908 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, uint32_t aWeight,
909 int32_t aStretch, uint8_t aStyle,
910 const nsTArray<gfxFontFeature>& aFeatureSettings,
911 const nsTArray<gfxFontVariation>& aVariationSettings,
912 uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges,
913 uint8_t aFontDisplay) {
914 RefPtr<gfxUserFontEntry> userFontEntry = new gfxUserFontEntry(
915 this, aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings,
916 aVariationSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay);
917 return userFontEntry.forget();
918 }
919
FindExistingUserFontEntry(gfxUserFontFamily * aFamily,const nsTArray<gfxFontFaceSrc> & aFontFaceSrcList,uint32_t aWeight,int32_t aStretch,uint8_t aStyle,const nsTArray<gfxFontFeature> & aFeatureSettings,const nsTArray<gfxFontVariation> & aVariationSettings,uint32_t aLanguageOverride,gfxCharacterMap * aUnicodeRanges,uint8_t aFontDisplay)920 gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry(
921 gfxUserFontFamily* aFamily,
922 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, uint32_t aWeight,
923 int32_t aStretch, uint8_t aStyle,
924 const nsTArray<gfxFontFeature>& aFeatureSettings,
925 const nsTArray<gfxFontVariation>& aVariationSettings,
926 uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges,
927 uint8_t aFontDisplay) {
928 MOZ_ASSERT(aWeight != 0,
929 "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
930
931 nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
932
933 for (size_t i = 0, count = fontList.Length(); i < count; i++) {
934 if (!fontList[i]->mIsUserFontContainer) {
935 continue;
936 }
937
938 gfxUserFontEntry* existingUserFontEntry =
939 static_cast<gfxUserFontEntry*>(fontList[i].get());
940 if (!existingUserFontEntry->Matches(aFontFaceSrcList, aWeight, aStretch,
941 aStyle, aFeatureSettings,
942 aVariationSettings, aLanguageOverride,
943 aUnicodeRanges, aFontDisplay)) {
944 continue;
945 }
946
947 return existingUserFontEntry;
948 }
949
950 return nullptr;
951 }
952
AddUserFontEntry(const nsAString & aFamilyName,gfxUserFontEntry * aUserFontEntry)953 void gfxUserFontSet::AddUserFontEntry(const nsAString& aFamilyName,
954 gfxUserFontEntry* aUserFontEntry) {
955 gfxUserFontFamily* family = GetFamily(aFamilyName);
956 family->AddFontEntry(aUserFontEntry);
957
958 if (LOG_ENABLED()) {
959 LOG(
960 ("userfonts (%p) added to \"%s\" (%p) style: %s weight: %d "
961 "stretch: %d display: %d",
962 this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aUserFontEntry,
963 (aUserFontEntry->IsItalic()
964 ? "italic"
965 : (aUserFontEntry->IsOblique() ? "oblique" : "normal")),
966 aUserFontEntry->Weight(), aUserFontEntry->Stretch(),
967 aUserFontEntry->GetFontDisplay()));
968 }
969 }
970
IncrementGeneration(bool aIsRebuild)971 void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) {
972 // add one, increment again if zero
973 ++sFontSetGeneration;
974 if (sFontSetGeneration == 0) ++sFontSetGeneration;
975 mGeneration = sFontSetGeneration;
976 if (aIsRebuild) {
977 mRebuildGeneration = mGeneration;
978 }
979 }
980
RebuildLocalRules()981 void gfxUserFontSet::RebuildLocalRules() {
982 if (mLocalRulesUsed) {
983 mRebuildLocalRules = true;
984 DoRebuildUserFontSet();
985 }
986 }
987
LookupFamily(const nsAString & aFamilyName) const988 gfxUserFontFamily* gfxUserFontSet::LookupFamily(
989 const nsAString& aFamilyName) const {
990 nsAutoString key(aFamilyName);
991 ToLowerCase(key);
992
993 return mFontFamilies.GetWeak(key);
994 }
995
ContainsUserFontSetFonts(const FontFamilyList & aFontList) const996 bool gfxUserFontSet::ContainsUserFontSetFonts(
997 const FontFamilyList& aFontList) const {
998 for (const FontFamilyName& name : aFontList.GetFontlist()->mNames) {
999 if (name.mType != eFamily_named && name.mType != eFamily_named_quoted) {
1000 continue;
1001 }
1002 if (LookupFamily(name.mName)) {
1003 return true;
1004 }
1005 }
1006 return false;
1007 }
1008
GetFamily(const nsAString & aFamilyName)1009 gfxUserFontFamily* gfxUserFontSet::GetFamily(const nsAString& aFamilyName) {
1010 nsAutoString key(aFamilyName);
1011 ToLowerCase(key);
1012
1013 gfxUserFontFamily* family = mFontFamilies.GetWeak(key);
1014 if (!family) {
1015 family = new gfxUserFontFamily(aFamilyName);
1016 mFontFamilies.Put(key, family);
1017 }
1018 return family;
1019 }
1020
1021 ///////////////////////////////////////////////////////////////////////////////
1022 // gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts
1023 // across pages/fontsets rather than instantiating new platform fonts.
1024 //
1025 // Entries are added to this cache when a platform font is instantiated from
1026 // downloaded data, and removed when the platform font entry is destroyed.
1027 // We don't need to use a timed expiration scheme here because the gfxFontEntry
1028 // for a downloaded font will be kept alive by its corresponding gfxFont
1029 // instance(s) until they are deleted, and *that* happens using an expiration
1030 // tracker (gfxFontCache). The result is that the downloaded font instances
1031 // recorded here will persist between pages and can get reused (provided the
1032 // source URI and principal match, of course).
1033 ///////////////////////////////////////////////////////////////////////////////
1034
1035 nsTHashtable<gfxUserFontSet::UserFontCache::Entry>*
1036 gfxUserFontSet::UserFontCache::sUserFonts = nullptr;
1037
1038 uint32_t gfxUserFontSet::UserFontCache::sGeneration = 0;
1039
NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher,nsIObserver)1040 NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver)
1041
1042 NS_IMETHODIMP
1043 gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject,
1044 const char* aTopic,
1045 const char16_t* aData) {
1046 if (!sUserFonts) {
1047 return NS_OK;
1048 }
1049
1050 if (!strcmp(aTopic, "cacheservice:empty-cache")) {
1051 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1052 i.Remove();
1053 }
1054 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1055 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1056 if (i.Get()->IsPrivate()) {
1057 i.Remove();
1058 }
1059 }
1060 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
1061 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1062 i.Get()->GetFontEntry()->DisconnectSVG();
1063 }
1064 } else {
1065 NS_NOTREACHED("unexpected topic");
1066 }
1067
1068 return NS_OK;
1069 }
1070
KeyEquals(const KeyTypePointer aKey) const1071 bool gfxUserFontSet::UserFontCache::Entry::KeyEquals(
1072 const KeyTypePointer aKey) const {
1073 const gfxFontEntry* fe = aKey->mFontEntry;
1074
1075 if (!mURI->Equals(aKey->mURI)) {
1076 return false;
1077 }
1078
1079 // For data: URIs, we don't care about the principal; otherwise, check it.
1080 if (!IgnorePrincipal(mURI)) {
1081 NS_ASSERTION(mPrincipal && aKey->mPrincipal,
1082 "only data: URIs are allowed to omit the principal");
1083 if (!mPrincipal->Equals(aKey->mPrincipal)) {
1084 return false;
1085 }
1086 }
1087
1088 if (mPrivate != aKey->mPrivate) {
1089 return false;
1090 }
1091
1092 if (mFontEntry->mStyle != fe->mStyle || mFontEntry->mWeight != fe->mWeight ||
1093 mFontEntry->mStretch != fe->mStretch ||
1094 mFontEntry->mFeatureSettings != fe->mFeatureSettings ||
1095 mFontEntry->mVariationSettings != fe->mVariationSettings ||
1096 mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
1097 mFontEntry->mFamilyName != fe->mFamilyName) {
1098 return false;
1099 }
1100
1101 return true;
1102 }
1103
CacheFont(gfxFontEntry * aFontEntry)1104 void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry) {
1105 NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0,
1106 "caching a font associated with no family yet");
1107
1108 // if caching is disabled, simply return
1109 if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
1110 return;
1111 }
1112
1113 gfxUserFontData* data = aFontEntry->mUserFontData.get();
1114 if (data->mIsBuffer) {
1115 #ifdef DEBUG_USERFONT_CACHE
1116 printf("userfontcache skipped fontentry with buffer source: %p\n",
1117 aFontEntry);
1118 #endif
1119 return;
1120 }
1121
1122 if (!sUserFonts) {
1123 sUserFonts = new nsTHashtable<Entry>;
1124
1125 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1126 if (obs) {
1127 Flusher* flusher = new Flusher;
1128 obs->AddObserver(flusher, "cacheservice:empty-cache", false);
1129 obs->AddObserver(flusher, "last-pb-context-exited", false);
1130 obs->AddObserver(flusher, "xpcom-shutdown", false);
1131 }
1132
1133 // Create and register a memory reporter for sUserFonts.
1134 // This reporter is never unregistered, but that's OK because
1135 // the reporter checks whether sUserFonts is null, so it would
1136 // be safe to call even after UserFontCache::Shutdown has deleted
1137 // the cache.
1138 RegisterStrongMemoryReporter(new MemoryReporter());
1139 }
1140
1141 // For data: URIs, the principal is ignored; anyone who has the same
1142 // data: URI is able to load it and get an equivalent font.
1143 // Otherwise, the principal is used as part of the cache key.
1144 gfxFontSrcPrincipal* principal;
1145 if (IgnorePrincipal(data->mURI)) {
1146 principal = nullptr;
1147 } else {
1148 principal = data->mPrincipal;
1149 }
1150 sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate));
1151
1152 ++sGeneration;
1153
1154 #ifdef DEBUG_USERFONT_CACHE
1155 printf("userfontcache added fontentry: %p\n", aFontEntry);
1156 Dump();
1157 #endif
1158 }
1159
ForgetFont(gfxFontEntry * aFontEntry)1160 void gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry* aFontEntry) {
1161 if (!sUserFonts) {
1162 // if we've already deleted the cache (i.e. during shutdown),
1163 // just ignore this
1164 return;
1165 }
1166
1167 // We can't simply use RemoveEntry here because it's possible the principal
1168 // may have changed since the font was cached, in which case the lookup
1169 // would no longer find the entry (bug 838105).
1170 for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
1171 if (i.Get()->GetFontEntry() == aFontEntry) {
1172 i.Remove();
1173 }
1174 }
1175
1176 #ifdef DEBUG_USERFONT_CACHE
1177 printf("userfontcache removed fontentry: %p\n", aFontEntry);
1178 Dump();
1179 #endif
1180 }
1181
GetFont(gfxFontSrcURI * aSrcURI,gfxFontSrcPrincipal * aPrincipal,gfxUserFontEntry * aUserFontEntry,bool aPrivate)1182 gfxFontEntry* gfxUserFontSet::UserFontCache::GetFont(
1183 gfxFontSrcURI* aSrcURI, gfxFontSrcPrincipal* aPrincipal,
1184 gfxUserFontEntry* aUserFontEntry, bool aPrivate) {
1185 if (!sUserFonts ||
1186 Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
1187 return nullptr;
1188 }
1189
1190 // Ignore principal when looking up a data: URI.
1191 gfxFontSrcPrincipal* principal;
1192 if (IgnorePrincipal(aSrcURI)) {
1193 principal = nullptr;
1194 } else {
1195 principal = aPrincipal;
1196 }
1197
1198 Entry* entry =
1199 sUserFonts->GetEntry(Key(aSrcURI, principal, aUserFontEntry, aPrivate));
1200 if (!entry) {
1201 return nullptr;
1202 }
1203
1204 // We have to perform another content policy check here to prevent
1205 // cache poisoning. E.g. a.com loads a font into the cache but
1206 // b.com has a CSP not allowing any fonts to be loaded.
1207 bool allowed = false;
1208 if (ServoStyleSet::IsInServoTraversal()) {
1209 // Use the cached IsFontLoadAllowed results in mAllowedFontSets.
1210 allowed = entry->CheckIsFontSetAllowedAndDispatchViolations(
1211 aUserFontEntry->mFontSet);
1212 } else {
1213 // Call IsFontLoadAllowed directly, since we are on the main thread.
1214 MOZ_ASSERT(NS_IsMainThread());
1215 nsIPrincipal* principal = aPrincipal ? aPrincipal->get() : nullptr;
1216 allowed =
1217 aUserFontEntry->mFontSet->IsFontLoadAllowed(aSrcURI->get(), principal,
1218 /* aViolations */ nullptr);
1219 MOZ_ASSERT(
1220 !entry->IsFontSetAllowedKnown(aUserFontEntry->mFontSet) ||
1221 entry->CheckIsFontSetAllowed(aUserFontEntry->mFontSet) == allowed,
1222 "why does IsFontLoadAllowed return a different value from "
1223 "the cached value in mAllowedFontSets?");
1224 }
1225
1226 if (!allowed) {
1227 return nullptr;
1228 }
1229
1230 return entry->GetFontEntry();
1231 }
1232
UpdateAllowedFontSets(gfxUserFontSet * aUserFontSet)1233 /* static */ void gfxUserFontSet::UserFontCache::UpdateAllowedFontSets(
1234 gfxUserFontSet* aUserFontSet) {
1235 MOZ_ASSERT(NS_IsMainThread());
1236
1237 if (!sUserFonts) {
1238 return;
1239 }
1240
1241 for (auto iter = sUserFonts->Iter(); !iter.Done(); iter.Next()) {
1242 Entry* entry = iter.Get();
1243 if (!entry->IsFontSetAllowedKnown(aUserFontSet)) {
1244 gfxFontSrcPrincipal* principal = entry->GetPrincipal();
1245 if (!principal) {
1246 // This is a data: URI. Just get the standard principal the
1247 // font set uses. (For cases when mUseOriginPrincipal is true,
1248 // we don't use the cached results of IsFontLoadAllowed, and
1249 // instead just process the data: URI load async.)
1250 principal = aUserFontSet->GetStandardFontLoadPrincipal();
1251 }
1252 nsTArray<nsCOMPtr<nsIRunnable>> violations;
1253 bool allowed = aUserFontSet->IsFontLoadAllowed(
1254 entry->GetURI()->get(), principal->get(), &violations);
1255 entry->SetIsFontSetAllowed(aUserFontSet, allowed, Move(violations));
1256 }
1257 }
1258 }
1259
ClearAllowedFontSets(gfxUserFontSet * aUserFontSet)1260 /* static */ void gfxUserFontSet::UserFontCache::ClearAllowedFontSets(
1261 gfxUserFontSet* aUserFontSet) {
1262 MOZ_ASSERT(NS_IsMainThread());
1263
1264 if (!sUserFonts) {
1265 return;
1266 }
1267
1268 for (auto iter = sUserFonts->Iter(); !iter.Done(); iter.Next()) {
1269 Entry* entry = iter.Get();
1270 entry->ClearIsFontSetAllowed(aUserFontSet);
1271 }
1272 }
1273
Shutdown()1274 void gfxUserFontSet::UserFontCache::Shutdown() {
1275 if (sUserFonts) {
1276 delete sUserFonts;
1277 sUserFonts = nullptr;
1278 }
1279 }
1280
MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)1281 MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)
1282
1283 bool gfxUserFontSet::UserFontCache::Entry::CheckIsFontSetAllowed(
1284 gfxUserFontSet* aUserFontSet) const {
1285 LoadResultEntry* entry = mAllowedFontSets.GetEntry(aUserFontSet);
1286 MOZ_ASSERT(entry,
1287 "UpdateAllowedFontSets should have been called and "
1288 "added an entry to mAllowedFontSets");
1289 return entry->mAllowed;
1290 }
1291
1292 bool gfxUserFontSet::UserFontCache::Entry::
CheckIsFontSetAllowedAndDispatchViolations(gfxUserFontSet * aUserFontSet) const1293 CheckIsFontSetAllowedAndDispatchViolations(
1294 gfxUserFontSet* aUserFontSet) const {
1295 LoadResultEntry* entry = mAllowedFontSets.GetEntry(aUserFontSet);
1296 MOZ_ASSERT(entry,
1297 "UpdateAllowedFontSets should have been called and "
1298 "added an entry to mAllowedFontSets");
1299 if (!entry->mViolations.IsEmpty()) {
1300 aUserFontSet->DispatchFontLoadViolations(entry->mViolations);
1301 }
1302 return entry->mAllowed;
1303 }
1304
IsFontSetAllowedKnown(gfxUserFontSet * aUserFontSet) const1305 bool gfxUserFontSet::UserFontCache::Entry::IsFontSetAllowedKnown(
1306 gfxUserFontSet* aUserFontSet) const {
1307 return mAllowedFontSets.Contains(aUserFontSet);
1308 }
1309
SetIsFontSetAllowed(gfxUserFontSet * aUserFontSet,bool aAllowed,nsTArray<nsCOMPtr<nsIRunnable>> && aViolations)1310 void gfxUserFontSet::UserFontCache::Entry::SetIsFontSetAllowed(
1311 gfxUserFontSet* aUserFontSet, bool aAllowed,
1312 nsTArray<nsCOMPtr<nsIRunnable>>&& aViolations) {
1313 MOZ_ASSERT(!IsFontSetAllowedKnown(aUserFontSet));
1314 LoadResultEntry* entry = mAllowedFontSets.PutEntry(aUserFontSet);
1315 entry->mAllowed = aAllowed;
1316 entry->mViolations.SwapElements(aViolations);
1317 }
1318
ClearIsFontSetAllowed(gfxUserFontSet * aUserFontSet)1319 void gfxUserFontSet::UserFontCache::Entry::ClearIsFontSetAllowed(
1320 gfxUserFontSet* aUserFontSet) {
1321 mAllowedFontSets.RemoveEntry(aUserFontSet);
1322 }
1323
ReportMemory(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1324 void gfxUserFontSet::UserFontCache::Entry::ReportMemory(
1325 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1326 bool aAnonymize) {
1327 MOZ_ASSERT(mFontEntry);
1328 nsAutoCString path("explicit/gfx/user-fonts/font(");
1329
1330 if (aAnonymize) {
1331 path.AppendPrintf("<anonymized-%p>", this);
1332 } else {
1333 NS_ConvertUTF16toUTF8 familyName(mFontEntry->mFamilyName);
1334 path.AppendPrintf("family=%s", familyName.get());
1335 if (mURI) {
1336 nsCString spec = mURI->GetSpecOrDefault();
1337 spec.ReplaceChar('/', '\\');
1338 // Some fonts are loaded using horrendously-long data: URIs;
1339 // truncate those before reporting them.
1340 bool isData;
1341 if (NS_SUCCEEDED(mURI->get()->SchemeIs("data", &isData)) && isData &&
1342 spec.Length() > 255) {
1343 spec.Truncate(252);
1344 spec.AppendLiteral("...");
1345 }
1346 path.AppendPrintf(", url=%s", spec.get());
1347 }
1348 if (mPrincipal) {
1349 nsCOMPtr<nsIURI> uri;
1350 mPrincipal->get()->GetURI(getter_AddRefs(uri));
1351 if (uri) {
1352 nsCString spec = uri->GetSpecOrDefault();
1353 if (!spec.IsEmpty()) {
1354 // Include a clue as to who loaded this resource. (Note
1355 // that because of font entry sharing, other pages may now
1356 // be using this resource, and the original page may not
1357 // even be loaded any longer.)
1358 spec.ReplaceChar('/', '\\');
1359 path.AppendPrintf(", principal=%s", spec.get());
1360 }
1361 }
1362 }
1363 }
1364 path.Append(')');
1365
1366 aHandleReport->Callback(
1367 EmptyCString(), path, nsIMemoryReporter::KIND_HEAP,
1368 nsIMemoryReporter::UNITS_BYTES,
1369 mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf),
1370 NS_LITERAL_CSTRING("Memory used by @font-face resource."), aData);
1371 }
1372
NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter,nsIMemoryReporter)1373 NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter,
1374 nsIMemoryReporter)
1375
1376 NS_IMETHODIMP
1377 gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports(
1378 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1379 bool aAnonymize) {
1380 if (!sUserFonts) {
1381 return NS_OK;
1382 }
1383
1384 for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
1385 it.Get()->ReportMemory(aHandleReport, aData, aAnonymize);
1386 }
1387
1388 MOZ_COLLECT_REPORT(
1389 "explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES,
1390 sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf),
1391 "Memory used by the @font-face cache, not counting the actual font "
1392 "resources.");
1393
1394 return NS_OK;
1395 }
1396
1397 #ifdef DEBUG_USERFONT_CACHE
1398
Dump()1399 void gfxUserFontSet::UserFontCache::Entry::Dump() {
1400 nsresult rv;
1401
1402 nsAutoCString principalURISpec("(null)");
1403 bool setDomain = false;
1404
1405 if (mPrincipal) {
1406 nsCOMPtr<nsIURI> principalURI;
1407 rv = mPrincipal->get()->GetURI(getter_AddRefs(principalURI));
1408 if (NS_SUCCEEDED(rv)) {
1409 principalURI->GetSpec(principalURISpec);
1410 }
1411
1412 nsCOMPtr<nsIURI> domainURI;
1413 mPrincipal->get()->GetDomain(getter_AddRefs(domainURI));
1414 if (domainURI) {
1415 setDomain = true;
1416 }
1417 }
1418
1419 NS_ASSERTION(mURI, "null URI in userfont cache entry");
1420
1421 printf(
1422 "userfontcache fontEntry: %p fonturihash: %8.8x "
1423 "family: %s domainset: %s principal: [%s]\n",
1424 mFontEntry, mURI->Hash(),
1425 NS_ConvertUTF16toUTF8(mFontEntry->FamilyName()).get(),
1426 setDomain ? "true" : "false", principalURISpec.get());
1427 }
1428
Dump()1429 void gfxUserFontSet::UserFontCache::Dump() {
1430 if (!sUserFonts) {
1431 return;
1432 }
1433
1434 printf("userfontcache dump count: %d ========\n", sUserFonts->Count());
1435 for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
1436 it.Get()->Dump();
1437 }
1438 printf("userfontcache dump ==================\n");
1439 }
1440
1441 #endif
1442
1443 #undef LOG
1444 #undef LOG_ENABLED
1445