Bumping manifests a=b2g-bump
[gecko.git] / gfx / thebes / gfxUserFontSet.cpp
blob1e9e87d3d489c6eb7487d761dc7d8cf9aa84b338
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/. */
6 #ifdef MOZ_LOGGING
7 #define FORCE_PR_LOG /* Allow logging in the release build */
8 #endif /* MOZ_LOGGING */
9 #include "prlog.h"
11 #include "gfxUserFontSet.h"
12 #include "gfxPlatform.h"
13 #include "nsUnicharUtils.h"
14 #include "nsNetUtil.h"
15 #include "nsIJARChannel.h"
16 #include "nsIProtocolHandler.h"
17 #include "nsIPrincipal.h"
18 #include "nsIZipReader.h"
19 #include "gfxFontConstants.h"
20 #include "mozilla/Services.h"
21 #include "mozilla/gfx/2D.h"
22 #include "gfxPlatformFontList.h"
24 #include "opentype-sanitiser.h"
25 #include "ots-memory-stream.h"
27 using namespace mozilla;
29 #ifdef PR_LOGGING
30 PRLogModuleInfo *
31 gfxUserFontSet::GetUserFontsLog()
33 static PRLogModuleInfo *sLog;
34 if (!sLog)
35 sLog = PR_NewLogModule("userfonts");
36 return sLog;
38 #endif /* PR_LOGGING */
40 #define LOG(args) PR_LOG(gfxUserFontSet::GetUserFontsLog(), PR_LOG_DEBUG, args)
41 #define LOG_ENABLED() PR_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), PR_LOG_DEBUG)
43 static uint64_t sFontSetGeneration = 0;
45 // Based on ots::ExpandingMemoryStream from ots-memory-stream.h,
46 // adapted to use Mozilla allocators and to allow the final
47 // memory buffer to be adopted by the client.
48 class ExpandingMemoryStream : public ots::OTSStream {
49 public:
50 ExpandingMemoryStream(size_t initial, size_t limit)
51 : mLength(initial), mLimit(limit), mOff(0) {
52 mPtr = NS_Alloc(mLength);
55 ~ExpandingMemoryStream() {
56 NS_Free(mPtr);
59 // return the buffer, and give up ownership of it
60 // so the caller becomes responsible to call NS_Free
61 // when finished with it
62 void* forget() {
63 void* p = mPtr;
64 mPtr = nullptr;
65 return p;
68 bool WriteRaw(const void *data, size_t length) {
69 if ((mOff + length > mLength) ||
70 (mLength > std::numeric_limits<size_t>::max() - mOff)) {
71 if (mLength == mLimit) {
72 return false;
74 size_t newLength = (mLength + 1) * 2;
75 if (newLength < mLength) {
76 return false;
78 if (newLength > mLimit) {
79 newLength = mLimit;
81 mPtr = NS_Realloc(mPtr, newLength);
82 mLength = newLength;
83 return WriteRaw(data, length);
85 std::memcpy(static_cast<char*>(mPtr) + mOff, data, length);
86 mOff += length;
87 return true;
90 bool Seek(off_t position) {
91 if (position < 0) {
92 return false;
94 if (static_cast<size_t>(position) > mLength) {
95 return false;
97 mOff = position;
98 return true;
101 off_t Tell() const {
102 return mOff;
105 private:
106 void* mPtr;
107 size_t mLength;
108 const size_t mLimit;
109 off_t mOff;
112 // TODO: support for unicode ranges not yet implemented
114 gfxProxyFontEntry::gfxProxyFontEntry(gfxUserFontSet *aFontSet,
115 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
116 uint32_t aWeight,
117 int32_t aStretch,
118 uint32_t aItalicStyle,
119 const nsTArray<gfxFontFeature>& aFeatureSettings,
120 uint32_t aLanguageOverride,
121 gfxSparseBitSet *aUnicodeRanges)
122 : gfxFontEntry(NS_LITERAL_STRING("Proxy")),
123 mLoadingState(NOT_LOADING),
124 mUnsupportedFormat(false),
125 mLoader(nullptr),
126 mFontSet(aFontSet)
128 mIsProxy = true;
129 mSrcList = aFontFaceSrcList;
130 mSrcIndex = 0;
131 mWeight = aWeight;
132 mStretch = aStretch;
133 // XXX Currently, we don't distinguish 'italic' and 'oblique' styles;
134 // we need to fix this. (Bug 543715)
135 mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
136 mFeatureSettings.AppendElements(aFeatureSettings);
137 mLanguageOverride = aLanguageOverride;
138 mIsUserFont = true;
141 gfxProxyFontEntry::~gfxProxyFontEntry()
145 bool
146 gfxProxyFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
147 uint32_t aWeight,
148 int32_t aStretch,
149 uint32_t aItalicStyle,
150 const nsTArray<gfxFontFeature>& aFeatureSettings,
151 uint32_t aLanguageOverride,
152 gfxSparseBitSet *aUnicodeRanges)
154 // XXX font entries don't distinguish italic from oblique (bug 543715)
155 bool isItalic =
156 (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
158 return mWeight == aWeight &&
159 mStretch == aStretch &&
160 mItalic == isItalic &&
161 mFeatureSettings == aFeatureSettings &&
162 mLanguageOverride == aLanguageOverride &&
163 mSrcList == aFontFaceSrcList;
164 // XXX once we support unicode-range (bug 475891),
165 // we'll need to compare that here as well
168 gfxFont*
169 gfxProxyFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
171 // cannot create an actual font for a proxy entry
172 return nullptr;
175 class gfxOTSContext : public ots::OTSContext {
176 public:
177 gfxOTSContext(gfxMixedFontFamily *aFamily, gfxProxyFontEntry *aProxy)
178 : mFamily(aFamily), mProxy(aProxy) {}
180 virtual ots::TableAction GetTableAction(uint32_t aTag) MOZ_OVERRIDE {
181 // preserve Graphite, color glyph and SVG tables
182 if (aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') ||
183 aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') ||
184 aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') ||
185 aTag == TRUETYPE_TAG('G', 'l', 'a', 't') ||
186 aTag == TRUETYPE_TAG('F', 'e', 'a', 't') ||
187 aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
188 aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
189 aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
190 return ots::TABLE_ACTION_PASSTHRU;
192 return ots::TABLE_ACTION_DEFAULT;
195 virtual void Message(int level, const char* format,
196 ...) MSGFUNC_FMT_ATTR MOZ_OVERRIDE {
197 va_list va;
198 va_start(va, format);
200 nsCString msg;
201 msg.AppendPrintf(format, va);
203 va_end(va);
205 if (level > 0) {
206 // For warnings (rather than errors that cause the font to fail),
207 // we only report the first instance of any given message.
208 if (mWarningsIssued.Contains(msg)) {
209 return;
211 mWarningsIssued.PutEntry(msg);
214 mProxy->mFontSet->LogMessage(mFamily, mProxy, msg.get());
217 private:
218 gfxMixedFontFamily *mFamily;
219 gfxProxyFontEntry *mProxy;
220 nsTHashtable<nsCStringHashKey> mWarningsIssued;
223 // Call the OTS library to sanitize an sfnt before attempting to use it.
224 // Returns a newly-allocated block, or nullptr in case of fatal errors.
225 const uint8_t*
226 gfxProxyFontEntry::SanitizeOpenTypeData(gfxMixedFontFamily *aFamily,
227 const uint8_t* aData,
228 uint32_t aLength,
229 uint32_t& aSaneLength,
230 bool aIsCompressed)
232 // limit output/expansion to 256MB
233 ExpandingMemoryStream output(aIsCompressed ? aLength * 2 : aLength,
234 1024 * 1024 * 256);
236 gfxOTSContext otsContext(aFamily, this);
238 if (otsContext.Process(&output, aData, aLength)) {
239 aSaneLength = output.Tell();
240 return static_cast<uint8_t*>(output.forget());
241 } else {
242 aSaneLength = 0;
243 return nullptr;
247 void
248 gfxProxyFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
249 bool aPrivate,
250 const nsAString& aOriginalName,
251 FallibleTArray<uint8_t>* aMetadata,
252 uint32_t aMetaOrigLen)
254 if (!aFontEntry->mUserFontData) {
255 aFontEntry->mUserFontData = new gfxUserFontData;
257 gfxUserFontData* userFontData = aFontEntry->mUserFontData;
258 userFontData->mSrcIndex = mSrcIndex;
259 const gfxFontFaceSrc& src = mSrcList[mSrcIndex];
260 if (src.mIsLocal) {
261 userFontData->mLocalName = src.mLocalName;
262 } else {
263 userFontData->mURI = src.mURI;
264 userFontData->mPrincipal = mPrincipal;
266 userFontData->mPrivate = aPrivate;
267 userFontData->mFormat = src.mFormatFlags;
268 userFontData->mRealName = aOriginalName;
269 if (aMetadata) {
270 userFontData->mMetadata.SwapElements(*aMetadata);
271 userFontData->mMetaOrigLen = aMetaOrigLen;
275 struct WOFFHeader {
276 AutoSwap_PRUint32 signature;
277 AutoSwap_PRUint32 flavor;
278 AutoSwap_PRUint32 length;
279 AutoSwap_PRUint16 numTables;
280 AutoSwap_PRUint16 reserved;
281 AutoSwap_PRUint32 totalSfntSize;
282 AutoSwap_PRUint16 majorVersion;
283 AutoSwap_PRUint16 minorVersion;
284 AutoSwap_PRUint32 metaOffset;
285 AutoSwap_PRUint32 metaCompLen;
286 AutoSwap_PRUint32 metaOrigLen;
287 AutoSwap_PRUint32 privOffset;
288 AutoSwap_PRUint32 privLen;
291 static void
292 CopyWOFFMetadata(const uint8_t* aFontData,
293 uint32_t aLength,
294 FallibleTArray<uint8_t>* aMetadata,
295 uint32_t* aMetaOrigLen)
297 // This function may be called with arbitrary, unvalidated "font" data
298 // from @font-face, so it needs to be careful to bounds-check, etc.,
299 // before trying to read anything.
300 // This just saves a copy of the compressed data block; it does NOT check
301 // that the block can be successfully decompressed, or that it contains
302 // well-formed/valid XML metadata.
303 if (aLength < sizeof(WOFFHeader)) {
304 return;
306 const WOFFHeader* woff = reinterpret_cast<const WOFFHeader*>(aFontData);
307 uint32_t metaOffset = woff->metaOffset;
308 uint32_t metaCompLen = woff->metaCompLen;
309 if (!metaOffset || !metaCompLen || !woff->metaOrigLen) {
310 return;
312 if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) {
313 return;
315 if (!aMetadata->SetLength(woff->metaCompLen)) {
316 return;
318 memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen);
319 *aMetaOrigLen = woff->metaOrigLen;
322 gfxProxyFontEntry::LoadStatus
323 gfxProxyFontEntry::LoadNext(gfxMixedFontFamily *aFamily,
324 bool& aLocalRulesUsed)
326 uint32_t numSrc = mSrcList.Length();
328 NS_ASSERTION(mSrcIndex < numSrc,
329 "already at the end of the src list for user font");
331 if (mLoadingState == NOT_LOADING) {
332 mLoadingState = LOADING_STARTED;
333 mUnsupportedFormat = false;
334 } else {
335 // we were already loading; move to the next source,
336 // but don't reset state - if we've already timed out,
337 // that counts against the new download
338 mSrcIndex++;
341 // load each src entry in turn, until a local face is found
342 // or a download begins successfully
343 while (mSrcIndex < numSrc) {
344 const gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex];
346 // src local ==> lookup and load immediately
348 if (currSrc.mIsLocal) {
349 gfxFontEntry *fe =
350 gfxPlatform::GetPlatform()->LookupLocalFont(this,
351 currSrc.mLocalName);
352 aLocalRulesUsed = true;
353 if (fe) {
354 LOG(("fontset (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n",
355 mFontSet, mSrcIndex,
356 NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
357 NS_ConvertUTF16toUTF8(mFamilyName).get(),
358 uint32_t(mFontSet->mGeneration)));
359 fe->mFeatureSettings.AppendElements(mFeatureSettings);
360 fe->mLanguageOverride = mLanguageOverride;
361 fe->mFamilyName = mFamilyName;
362 // For src:local(), we don't care whether the request is from
363 // a private window as there's no issue of caching resources;
364 // local fonts are just available all the time.
365 StoreUserFontData(fe, false, nsString(), nullptr, 0);
366 mFontSet->ReplaceFontEntry(aFamily, this, fe);
367 return STATUS_LOADED;
368 } else {
369 LOG(("fontset (%p) [src %d] failed local: (%s) for (%s)\n",
370 mFontSet, mSrcIndex,
371 NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
372 NS_ConvertUTF16toUTF8(mFamilyName).get()));
376 // src url ==> start the load process
377 else {
378 if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI,
379 currSrc.mFormatFlags)) {
381 nsIPrincipal *principal = nullptr;
382 bool bypassCache;
383 nsresult rv = mFontSet->CheckFontLoad(&currSrc, &principal,
384 &bypassCache);
386 if (NS_SUCCEEDED(rv) && principal != nullptr) {
387 if (!bypassCache) {
388 // see if we have an existing entry for this source
389 gfxFontEntry *fe = gfxUserFontSet::
390 UserFontCache::GetFont(currSrc.mURI,
391 principal,
392 this,
393 mFontSet->GetPrivateBrowsing());
394 if (fe) {
395 mFontSet->ReplaceFontEntry(aFamily, this, fe);
396 return STATUS_LOADED;
400 // record the principal returned by CheckFontLoad,
401 // for use when creating a channel
402 // and when caching the loaded entry
403 mPrincipal = principal;
405 bool loadDoesntSpin = false;
406 rv = NS_URIChainHasFlags(currSrc.mURI,
407 nsIProtocolHandler::URI_SYNC_LOAD_IS_OK,
408 &loadDoesntSpin);
410 if (NS_SUCCEEDED(rv) && loadDoesntSpin) {
411 uint8_t *buffer = nullptr;
412 uint32_t bufferLength = 0;
414 // sync load font immediately
415 rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer,
416 bufferLength);
418 if (NS_SUCCEEDED(rv) &&
419 LoadFont(aFamily, buffer, bufferLength)) {
420 return STATUS_LOADED;
421 } else {
422 mFontSet->LogMessage(aFamily, this,
423 "font load failed",
424 nsIScriptError::errorFlag,
425 rv);
428 } else {
429 // otherwise load font async
430 rv = mFontSet->StartLoad(aFamily, this, &currSrc);
431 bool loadOK = NS_SUCCEEDED(rv);
433 if (loadOK) {
434 #ifdef PR_LOGGING
435 if (LOG_ENABLED()) {
436 nsAutoCString fontURI;
437 currSrc.mURI->GetSpec(fontURI);
438 LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n",
439 mFontSet, mSrcIndex, fontURI.get(),
440 NS_ConvertUTF16toUTF8(mFamilyName).get()));
442 #endif
443 return STATUS_LOADING;
444 } else {
445 mFontSet->LogMessage(aFamily, this,
446 "download failed",
447 nsIScriptError::errorFlag,
448 rv);
451 } else {
452 mFontSet->LogMessage(aFamily, this, "download not allowed",
453 nsIScriptError::errorFlag, rv);
455 } else {
456 // We don't log a warning to the web console yet,
457 // as another source may load successfully
458 mUnsupportedFormat = true;
462 mSrcIndex++;
465 if (mUnsupportedFormat) {
466 mFontSet->LogMessage(aFamily, this, "no supported format found",
467 nsIScriptError::warningFlag);
470 // all src's failed; mark this entry as unusable (so fallback will occur)
471 LOG(("userfonts (%p) failed all src for (%s)\n",
472 mFontSet, NS_ConvertUTF16toUTF8(mFamilyName).get()));
473 mLoadingState = LOADING_FAILED;
475 return STATUS_END_OF_LIST;
478 gfxFontEntry*
479 gfxProxyFontEntry::LoadFont(gfxMixedFontFamily *aFamily,
480 const uint8_t *aFontData, uint32_t &aLength)
482 gfxFontEntry *fe = nullptr;
484 gfxUserFontType fontType =
485 gfxFontUtils::DetermineFontDataType(aFontData, aLength);
487 // Unwrap/decompress/sanitize or otherwise munge the downloaded data
488 // to make a usable sfnt structure.
490 // Because platform font activation code may replace the name table
491 // in the font with a synthetic one, we save the original name so that
492 // it can be reported via the nsIDOMFontFace API.
493 nsAutoString originalFullName;
495 // Call the OTS sanitizer; this will also decode WOFF to sfnt
496 // if necessary. The original data in aFontData is left unchanged.
497 uint32_t saneLen;
498 const uint8_t* saneData =
499 SanitizeOpenTypeData(aFamily, aFontData, aLength, saneLen,
500 fontType == GFX_USERFONT_WOFF);
501 if (!saneData) {
502 mFontSet->LogMessage(aFamily, this, "rejected by sanitizer");
504 if (saneData) {
505 // The sanitizer ensures that we have a valid sfnt and a usable
506 // name table, so this should never fail unless we're out of
507 // memory, and GetFullNameFromSFNT is not directly exposed to
508 // arbitrary/malicious data from the web.
509 gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen,
510 originalFullName);
511 // Here ownership of saneData is passed to the platform,
512 // which will delete it when no longer required
513 fe = gfxPlatform::GetPlatform()->MakePlatformFont(this,
514 saneData,
515 saneLen);
516 if (!fe) {
517 mFontSet->LogMessage(aFamily, this, "not usable by platform");
521 if (fe) {
522 // Save a copy of the metadata block (if present) for nsIDOMFontFace
523 // to use if required. Ownership of the metadata block will be passed
524 // to the gfxUserFontData record below.
525 FallibleTArray<uint8_t> metadata;
526 uint32_t metaOrigLen = 0;
527 if (fontType == GFX_USERFONT_WOFF) {
528 CopyWOFFMetadata(aFontData, aLength, &metadata, &metaOrigLen);
531 // copy OpenType feature/language settings from the proxy to the
532 // newly-created font entry
533 fe->mFeatureSettings.AppendElements(mFeatureSettings);
534 fe->mLanguageOverride = mLanguageOverride;
535 fe->mFamilyName = mFamilyName;
536 StoreUserFontData(fe, mFontSet->GetPrivateBrowsing(), originalFullName,
537 &metadata, metaOrigLen);
538 #ifdef PR_LOGGING
539 if (LOG_ENABLED()) {
540 nsAutoCString fontURI;
541 mSrcList[mSrcIndex].mURI->GetSpec(fontURI);
542 LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) gen: %8.8x\n",
543 this, mSrcIndex, fontURI.get(),
544 NS_ConvertUTF16toUTF8(mFamilyName).get(),
545 uint32_t(mFontSet->mGeneration)));
547 #endif
548 mFontSet->ReplaceFontEntry(aFamily, this, fe);
549 gfxUserFontSet::UserFontCache::CacheFont(fe);
550 } else {
551 #ifdef PR_LOGGING
552 if (LOG_ENABLED()) {
553 nsAutoCString fontURI;
554 mSrcList[mSrcIndex].mURI->GetSpec(fontURI);
555 LOG(("userfonts (%p) [src %d] failed uri: (%s) for (%s)"
556 " error making platform font\n",
557 this, mSrcIndex, fontURI.get(),
558 NS_ConvertUTF16toUTF8(mFamilyName).get()));
560 #endif
563 // The downloaded data can now be discarded; the font entry is using the
564 // sanitized copy
565 NS_Free((void*)aFontData);
567 return fe;
570 gfxUserFontSet::gfxUserFontSet()
571 : mFontFamilies(4), mLocalRulesUsed(false)
573 IncrementGeneration();
574 gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList();
575 if (fp) {
576 fp->AddUserFontSet(this);
580 gfxUserFontSet::~gfxUserFontSet()
582 gfxPlatformFontList *fp = gfxPlatformFontList::PlatformFontList();
583 if (fp) {
584 fp->RemoveUserFontSet(this);
588 already_AddRefed<gfxProxyFontEntry>
589 gfxUserFontSet::FindOrCreateFontFace(
590 const nsAString& aFamilyName,
591 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
592 uint32_t aWeight,
593 int32_t aStretch,
594 uint32_t aItalicStyle,
595 const nsTArray<gfxFontFeature>& aFeatureSettings,
596 uint32_t aLanguageOverride,
597 gfxSparseBitSet* aUnicodeRanges)
599 nsRefPtr<gfxProxyFontEntry> entry;
601 // If there's already a proxy in the family whose descriptors all match,
602 // we can just move it to the end of the list instead of adding a new
603 // face that will always "shadow" the old one.
604 // Note that we can't do this for "real" (non-proxy) entries, even if the
605 // style descriptors match, as they might have had a different source list,
606 // but we no longer have the old source list available to check.
607 gfxMixedFontFamily* family = LookupFamily(aFamilyName);
608 if (family) {
609 entry = FindExistingProxyEntry(family, aFontFaceSrcList, aWeight,
610 aStretch, aItalicStyle, aFeatureSettings,
611 aLanguageOverride, aUnicodeRanges);
614 if (!entry) {
615 entry = CreateFontFace(aFontFaceSrcList, aWeight, aStretch,
616 aItalicStyle, aFeatureSettings, aLanguageOverride,
617 aUnicodeRanges);
618 entry->mFamilyName = aFamilyName;
620 #ifdef PR_LOGGING
621 if (LOG_ENABLED()) {
622 LOG(("userfonts (%p) created \"%s\" (%p) with style: %s weight: %d "
623 "stretch: %d",
624 this, NS_ConvertUTF16toUTF8(aFamilyName).get(), entry.get(),
625 (aItalicStyle & NS_FONT_STYLE_ITALIC ? "italic" :
626 (aItalicStyle & NS_FONT_STYLE_OBLIQUE ? "oblique" : "normal")),
627 aWeight, aStretch));
629 #endif
632 return entry.forget();
635 already_AddRefed<gfxProxyFontEntry>
636 gfxUserFontSet::CreateFontFace(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
637 uint32_t aWeight,
638 int32_t aStretch,
639 uint32_t aItalicStyle,
640 const nsTArray<gfxFontFeature>& aFeatureSettings,
641 uint32_t aLanguageOverride,
642 gfxSparseBitSet* aUnicodeRanges)
644 MOZ_ASSERT(aWeight != 0,
645 "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
647 nsRefPtr<gfxProxyFontEntry> proxyEntry =
648 new gfxProxyFontEntry(this, aFontFaceSrcList, aWeight,
649 aStretch, aItalicStyle, aFeatureSettings,
650 aLanguageOverride, aUnicodeRanges);
651 return proxyEntry.forget();
654 gfxProxyFontEntry*
655 gfxUserFontSet::FindExistingProxyEntry(
656 gfxMixedFontFamily* aFamily,
657 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
658 uint32_t aWeight,
659 int32_t aStretch,
660 uint32_t aItalicStyle,
661 const nsTArray<gfxFontFeature>& aFeatureSettings,
662 uint32_t aLanguageOverride,
663 gfxSparseBitSet* aUnicodeRanges)
665 MOZ_ASSERT(aWeight != 0,
666 "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
668 nsTArray<nsRefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
670 for (size_t i = 0, count = fontList.Length(); i < count; i++) {
671 if (!fontList[i]->mIsProxy) {
672 continue;
675 gfxProxyFontEntry* existingProxyEntry =
676 static_cast<gfxProxyFontEntry*>(fontList[i].get());
677 if (!existingProxyEntry->Matches(aFontFaceSrcList,
678 aWeight, aStretch, aItalicStyle,
679 aFeatureSettings, aLanguageOverride,
680 aUnicodeRanges)) {
681 continue;
684 return existingProxyEntry;
687 return nullptr;
690 void
691 gfxUserFontSet::AddFontFace(const nsAString& aFamilyName,
692 gfxFontEntry *aFontEntry)
694 gfxMixedFontFamily* family = GetFamily(aFamilyName);
695 family->AddFontEntry(aFontEntry);
697 #ifdef PR_LOGGING
698 if (LOG_ENABLED()) {
699 LOG(("userfonts (%p) added \"%s\" (%p)",
700 this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aFontEntry));
702 #endif
705 gfxFontEntry*
706 gfxUserFontSet::FindFontEntry(gfxFontFamily *aFamily,
707 const gfxFontStyle& aFontStyle,
708 bool& aNeedsBold,
709 bool& aWaitForUserFont)
711 aWaitForUserFont = false;
712 gfxMixedFontFamily *family = static_cast<gfxMixedFontFamily*>(aFamily);
714 gfxFontEntry* fe = family->FindFontForStyle(aFontStyle, aNeedsBold);
716 // if not a proxy, font has already been loaded
717 if (!fe->mIsProxy) {
718 return fe;
721 gfxProxyFontEntry *proxyEntry = static_cast<gfxProxyFontEntry*> (fe);
723 // if currently loading, return null for now
724 if (proxyEntry->mLoadingState > gfxProxyFontEntry::NOT_LOADING) {
725 aWaitForUserFont =
726 (proxyEntry->mLoadingState < gfxProxyFontEntry::LOADING_SLOWLY);
727 return nullptr;
730 // hasn't been loaded yet, start the load process
731 gfxProxyFontEntry::LoadStatus status;
733 // NOTE that if all sources in the entry fail, this will delete proxyEntry,
734 // so we cannot use it again if status==STATUS_END_OF_LIST
735 status = proxyEntry->LoadNext(family, mLocalRulesUsed);
737 // if the load succeeded immediately, the font entry was replaced so
738 // search again
739 if (status == gfxProxyFontEntry::STATUS_LOADED) {
740 return family->FindFontForStyle(aFontStyle, aNeedsBold);
743 // check whether we should wait for load to complete before painting
744 // a fallback font -- but not if all sources failed (bug 633500)
745 aWaitForUserFont = (status != gfxProxyFontEntry::STATUS_END_OF_LIST) &&
746 (proxyEntry->mLoadingState < gfxProxyFontEntry::LOADING_SLOWLY);
748 // if either loading or an error occurred, return null
749 return nullptr;
752 // This is called when a font download finishes.
753 // Ownership of aFontData passes in here, and the font set must
754 // ensure that it is eventually deleted via NS_Free().
755 bool
756 gfxUserFontSet::OnLoadComplete(gfxMixedFontFamily *aFamily,
757 gfxProxyFontEntry *aProxy,
758 const uint8_t *aFontData, uint32_t aLength,
759 nsresult aDownloadStatus)
761 // forget about the loader, as we no longer potentially need to cancel it
762 // if the entry is obsoleted
763 aProxy->mLoader = nullptr;
765 // download successful, make platform font using font data
766 if (NS_SUCCEEDED(aDownloadStatus)) {
767 gfxFontEntry *fe = aProxy->LoadFont(aFamily, aFontData, aLength);
768 aFontData = nullptr;
770 if (fe) {
771 IncrementGeneration();
772 return true;
775 } else {
776 // download failed
777 LogMessage(aFamily, aProxy,
778 "download failed", nsIScriptError::errorFlag,
779 aDownloadStatus);
782 if (aFontData) {
783 moz_free((void*)aFontData);
786 // error occurred, load next src
787 (void)aProxy->LoadNext(aFamily, mLocalRulesUsed);
789 // We ignore the status returned by LoadNext();
790 // even if loading failed, we need to bump the font-set generation
791 // and return true in order to trigger reflow, so that fallback
792 // will be used where the text was "masked" by the pending download
793 IncrementGeneration();
794 return true;
797 void
798 gfxUserFontSet::IncrementGeneration()
800 // add one, increment again if zero
801 ++sFontSetGeneration;
802 if (sFontSetGeneration == 0)
803 ++sFontSetGeneration;
804 mGeneration = sFontSetGeneration;
807 void
808 gfxUserFontSet::RebuildLocalRules()
810 if (mLocalRulesUsed) {
811 DoRebuildUserFontSet();
815 gfxMixedFontFamily*
816 gfxUserFontSet::LookupFamily(const nsAString& aFamilyName) const
818 nsAutoString key(aFamilyName);
819 ToLowerCase(key);
821 return mFontFamilies.GetWeak(key);
824 gfxMixedFontFamily*
825 gfxUserFontSet::GetFamily(const nsAString& aFamilyName)
827 nsAutoString key(aFamilyName);
828 ToLowerCase(key);
830 gfxMixedFontFamily* family = mFontFamilies.GetWeak(key);
831 if (!family) {
832 family = new gfxMixedFontFamily(aFamilyName);
833 mFontFamilies.Put(key, family);
835 return family;
838 struct FindFamilyCallbackData {
839 gfxFontEntry *mFontEntry;
840 gfxFontFamily *mFamily;
843 static PLDHashOperator
844 FindFamilyCallback(const nsAString& aName,
845 gfxMixedFontFamily* aFamily,
846 void* aUserArg)
848 FindFamilyCallbackData *d = static_cast<FindFamilyCallbackData*>(aUserArg);
849 if (aFamily->ContainsFace(d->mFontEntry)) {
850 d->mFamily = aFamily;
851 return PL_DHASH_STOP;
854 return PL_DHASH_NEXT;
857 gfxFontFamily*
858 gfxUserFontSet::FindFamilyFor(gfxFontEntry* aFontEntry) const
860 FindFamilyCallbackData d = { aFontEntry, nullptr };
861 mFontFamilies.EnumerateRead(FindFamilyCallback, &d);
862 return d.mFamily;
865 ///////////////////////////////////////////////////////////////////////////////
866 // gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts
867 // across pages/fontsets rather than instantiating new platform fonts.
869 // Entries are added to this cache when a platform font is instantiated from
870 // downloaded data, and removed when the platform font entry is destroyed.
871 // We don't need to use a timed expiration scheme here because the gfxFontEntry
872 // for a downloaded font will be kept alive by its corresponding gfxFont
873 // instance(s) until they are deleted, and *that* happens using an expiration
874 // tracker (gfxFontCache). The result is that the downloaded font instances
875 // recorded here will persist between pages and can get reused (provided the
876 // source URI and principal match, of course).
877 ///////////////////////////////////////////////////////////////////////////////
879 nsTHashtable<gfxUserFontSet::UserFontCache::Entry>*
880 gfxUserFontSet::UserFontCache::sUserFonts = nullptr;
882 NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver)
884 PLDHashOperator
885 gfxUserFontSet::UserFontCache::Entry::RemoveUnlessPersistent(Entry* aEntry,
886 void* aUserData)
888 return aEntry->mPersistence == kPersistent ? PL_DHASH_NEXT :
889 PL_DHASH_REMOVE;
892 PLDHashOperator
893 gfxUserFontSet::UserFontCache::Entry::RemoveIfPrivate(Entry* aEntry,
894 void* aUserData)
896 return aEntry->mPrivate ? PL_DHASH_REMOVE : PL_DHASH_NEXT;
899 PLDHashOperator
900 gfxUserFontSet::UserFontCache::Entry::RemoveIfMatches(Entry* aEntry,
901 void* aUserData)
903 return aEntry->GetFontEntry() == static_cast<gfxFontEntry*>(aUserData) ?
904 PL_DHASH_REMOVE : PL_DHASH_NEXT;
907 PLDHashOperator
908 gfxUserFontSet::UserFontCache::Entry::DisconnectSVG(Entry* aEntry,
909 void* aUserData)
911 aEntry->GetFontEntry()->DisconnectSVG();
912 return PL_DHASH_NEXT;
915 NS_IMETHODIMP
916 gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject,
917 const char* aTopic,
918 const char16_t* aData)
920 if (!sUserFonts) {
921 return NS_OK;
924 if (!strcmp(aTopic, "cacheservice:empty-cache")) {
925 sUserFonts->EnumerateEntries(Entry::RemoveUnlessPersistent, nullptr);
926 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
927 sUserFonts->EnumerateEntries(Entry::RemoveIfPrivate, nullptr);
928 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
929 sUserFonts->EnumerateEntries(Entry::DisconnectSVG, nullptr);
930 } else {
931 NS_NOTREACHED("unexpected topic");
934 return NS_OK;
937 static bool
938 IgnorePrincipal(nsIURI *aURI)
940 nsresult rv;
941 bool inherits = false;
942 rv = NS_URIChainHasFlags(aURI,
943 nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
944 &inherits);
945 return NS_SUCCEEDED(rv) && inherits;
948 bool
949 gfxUserFontSet::UserFontCache::Entry::KeyEquals(const KeyTypePointer aKey) const
951 const gfxFontEntry *fe = aKey->mFontEntry;
952 // CRC32 checking mode
953 if (mLength || aKey->mLength) {
954 if (aKey->mLength != mLength ||
955 aKey->mCRC32 != mCRC32) {
956 return false;
958 } else {
959 bool result;
960 if (NS_FAILED(mURI->Equals(aKey->mURI, &result)) || !result) {
961 return false;
964 // For data: URIs, we don't care about the principal; otherwise, check it.
965 if (!IgnorePrincipal(mURI)) {
966 NS_ASSERTION(mPrincipal && aKey->mPrincipal,
967 "only data: URIs are allowed to omit the principal");
968 if (NS_FAILED(mPrincipal->Equals(aKey->mPrincipal, &result)) ||
969 !result) {
970 return false;
974 if (mPrivate != aKey->mPrivate) {
975 return false;
979 if (mFontEntry->mItalic != fe->mItalic ||
980 mFontEntry->mWeight != fe->mWeight ||
981 mFontEntry->mStretch != fe->mStretch ||
982 mFontEntry->mFeatureSettings != fe->mFeatureSettings ||
983 mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
984 mFontEntry->mFamilyName != fe->mFamilyName) {
985 return false;
988 return true;
991 void
992 gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry *aFontEntry,
993 EntryPersistence aPersistence)
995 NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0,
996 "caching a font associated with no family yet");
997 if (!sUserFonts) {
998 sUserFonts = new nsTHashtable<Entry>;
1000 nsCOMPtr<nsIObserverService> obs =
1001 mozilla::services::GetObserverService();
1002 if (obs) {
1003 Flusher *flusher = new Flusher;
1004 obs->AddObserver(flusher, "cacheservice:empty-cache",
1005 false);
1006 obs->AddObserver(flusher, "last-pb-context-exited", false);
1007 obs->AddObserver(flusher, "xpcom-shutdown", false);
1011 gfxUserFontData *data = aFontEntry->mUserFontData;
1012 if (data->mLength) {
1013 MOZ_ASSERT(aPersistence == kPersistent);
1014 MOZ_ASSERT(!data->mPrivate);
1015 sUserFonts->PutEntry(Key(data->mCRC32, data->mLength, aFontEntry,
1016 data->mPrivate, aPersistence));
1017 } else {
1018 MOZ_ASSERT(aPersistence == kDiscardable);
1019 // For data: URIs, the principal is ignored; anyone who has the same
1020 // data: URI is able to load it and get an equivalent font.
1021 // Otherwise, the principal is used as part of the cache key.
1022 nsIPrincipal *principal;
1023 if (IgnorePrincipal(data->mURI)) {
1024 principal = nullptr;
1025 } else {
1026 principal = data->mPrincipal;
1028 sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry,
1029 data->mPrivate, aPersistence));
1032 #ifdef DEBUG_USERFONT_CACHE
1033 printf("userfontcache added fontentry: %p\n", aFontEntry);
1034 Dump();
1035 #endif
1038 void
1039 gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry *aFontEntry)
1041 if (!sUserFonts) {
1042 // if we've already deleted the cache (i.e. during shutdown),
1043 // just ignore this
1044 return;
1047 // We can't simply use RemoveEntry here because it's possible the principal
1048 // may have changed since the font was cached, in which case the lookup
1049 // would no longer find the entry (bug 838105).
1050 sUserFonts->EnumerateEntries(
1051 gfxUserFontSet::UserFontCache::Entry::RemoveIfMatches, aFontEntry);
1053 #ifdef DEBUG_USERFONT_CACHE
1054 printf("userfontcache removed fontentry: %p\n", aFontEntry);
1055 Dump();
1056 #endif
1059 gfxFontEntry*
1060 gfxUserFontSet::UserFontCache::GetFont(nsIURI *aSrcURI,
1061 nsIPrincipal *aPrincipal,
1062 gfxProxyFontEntry *aProxy,
1063 bool aPrivate)
1065 if (!sUserFonts) {
1066 return nullptr;
1069 // Ignore principal when looking up a data: URI.
1070 nsIPrincipal *principal;
1071 if (IgnorePrincipal(aSrcURI)) {
1072 principal = nullptr;
1073 } else {
1074 principal = aPrincipal;
1077 Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, principal, aProxy,
1078 aPrivate));
1079 if (entry) {
1080 return entry->GetFontEntry();
1083 nsCOMPtr<nsIChannel> chan;
1084 if (NS_FAILED(NS_NewChannel(getter_AddRefs(chan), aSrcURI))) {
1085 return nullptr;
1088 nsCOMPtr<nsIJARChannel> jarchan = do_QueryInterface(chan);
1089 if (!jarchan) {
1090 return nullptr;
1093 nsCOMPtr<nsIZipEntry> zipentry;
1094 if (NS_FAILED(jarchan->GetZipEntry(getter_AddRefs(zipentry)))) {
1095 return nullptr;
1098 uint32_t crc32, length;
1099 zipentry->GetCRC32(&crc32);
1100 zipentry->GetRealSize(&length);
1102 entry = sUserFonts->GetEntry(Key(crc32, length, aProxy, aPrivate));
1103 if (entry) {
1104 return entry->GetFontEntry();
1107 return nullptr;
1110 void
1111 gfxUserFontSet::UserFontCache::Shutdown()
1113 if (sUserFonts) {
1114 delete sUserFonts;
1115 sUserFonts = nullptr;
1119 #ifdef DEBUG_USERFONT_CACHE
1121 PLDHashOperator
1122 gfxUserFontSet::UserFontCache::Entry::DumpEntry(Entry* aEntry, void* aUserData)
1124 nsresult rv;
1126 nsAutoCString principalURISpec("(null)");
1127 bool setDomain = false;
1129 if (aEntry->mPrincipal) {
1130 nsCOMPtr<nsIURI> principalURI;
1131 rv = aEntry->mPrincipal->GetURI(getter_AddRefs(principalURI));
1132 if (NS_SUCCEEDED(rv)) {
1133 principalURI->GetSpec(principalURISpec);
1136 nsCOMPtr<nsIURI> domainURI;
1137 aEntry->mPrincipal->GetDomain(getter_AddRefs(domainURI));
1138 if (domainURI) {
1139 setDomain = true;
1143 NS_ASSERTION(aEntry->mURI, "null URI in userfont cache entry");
1145 printf("userfontcache fontEntry: %p fonturihash: %8.8x family: %s domainset: %s principal: [%s]\n",
1146 aEntry->mFontEntry,
1147 nsURIHashKey::HashKey(aEntry->mURI),
1148 NS_ConvertUTF16toUTF8(aEntry->mFontEntry->FamilyName()).get(),
1149 (setDomain ? "true" : "false"),
1150 principalURISpec.get()
1152 return PL_DHASH_NEXT;
1155 void
1156 gfxUserFontSet::UserFontCache::Dump()
1158 if (!sUserFonts) {
1159 return;
1162 printf("userfontcache dump count: %d ========\n", sUserFonts->Count());
1163 sUserFonts->EnumerateEntries(Entry::DumpEntry, nullptr);
1164 printf("userfontcache dump ==================\n");
1167 #endif