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