Bug 1698786: part 2) Change some compile-time dependent `printf`s to `MOZ_LOG` in...
[gecko.git] / layout / style / FontFaceSet.cpp
blob697109b1349921beaa8fa2eaafba062b4ae62d42
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FontFaceSet.h"
9 #include "gfxFontConstants.h"
10 #include "gfxFontSrcPrincipal.h"
11 #include "gfxFontSrcURI.h"
12 #include "FontPreloader.h"
13 #include "mozilla/css/Loader.h"
14 #include "mozilla/dom/CSSFontFaceRule.h"
15 #include "mozilla/dom/DocumentInlines.h"
16 #include "mozilla/dom/Event.h"
17 #include "mozilla/dom/FontFaceSetBinding.h"
18 #include "mozilla/dom/FontFaceSetIterator.h"
19 #include "mozilla/dom/FontFaceSetLoadEvent.h"
20 #include "mozilla/dom/FontFaceSetLoadEventBinding.h"
21 #include "mozilla/dom/Promise.h"
22 #include "mozilla/FontPropertyTypes.h"
23 #include "mozilla/AsyncEventDispatcher.h"
24 #include "mozilla/BasePrincipal.h"
25 #include "mozilla/Logging.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/PresShellInlines.h"
29 #include "mozilla/ServoBindings.h"
30 #include "mozilla/ServoCSSParser.h"
31 #include "mozilla/ServoStyleSet.h"
32 #include "mozilla/ServoUtils.h"
33 #include "mozilla/Sprintf.h"
34 #include "mozilla/StaticPrefs_layout.h"
35 #include "mozilla/Telemetry.h"
36 #include "mozilla/LoadInfo.h"
37 #include "nsComponentManagerUtils.h"
38 #include "nsContentPolicyUtils.h"
39 #include "nsContentUtils.h"
40 #include "nsDeviceContext.h"
41 #include "nsFontFaceLoader.h"
42 #include "nsIConsoleService.h"
43 #include "nsIContentPolicy.h"
44 #include "nsIDocShell.h"
45 #include "mozilla/dom/Document.h"
46 #include "nsILoadContext.h"
47 #include "nsINetworkPredictor.h"
48 #include "nsIPrincipal.h"
49 #include "nsIWebNavigation.h"
50 #include "nsNetUtil.h"
51 #include "nsIInputStream.h"
52 #include "nsLayoutUtils.h"
53 #include "nsPresContext.h"
54 #include "nsPrintfCString.h"
55 #include "nsUTF8Utils.h"
56 #include "nsDOMNavigationTiming.h"
57 #include "ReferrerInfo.h"
59 using namespace mozilla;
60 using namespace mozilla::css;
61 using namespace mozilla::dom;
63 #define LOG(args) \
64 MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
65 #define LOG_ENABLED() \
66 MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
68 NS_IMPL_CYCLE_COLLECTION_CLASS(FontFaceSet)
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FontFaceSet,
71 DOMEventTargetHelper)
72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument);
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady);
74 for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) {
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleFaces[i].mFontFace);
77 for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) {
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonRuleFaces[i].mFontFace);
80 if (tmp->mUserFontSet) {
81 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUserFontSet->mFontFaceSet);
83 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
85 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FontFaceSet,
86 DOMEventTargetHelper)
87 tmp->Disconnect();
88 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument);
89 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady);
90 for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) {
91 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRuleFaces[i].mFontFace);
93 for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) {
94 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonRuleFaces[i].mFontFace);
96 if (tmp->mUserFontSet) {
97 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUserFontSet->mFontFaceSet);
99 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUserFontSet);
100 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
102 NS_IMPL_ADDREF_INHERITED(FontFaceSet, DOMEventTargetHelper)
103 NS_IMPL_RELEASE_INHERITED(FontFaceSet, DOMEventTargetHelper)
105 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FontFaceSet)
106 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
107 NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
108 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
110 FontFaceSet::FontFaceSet(nsPIDOMWindowInner* aWindow, dom::Document* aDocument)
111 : DOMEventTargetHelper(aWindow),
112 mDocument(aDocument),
113 mStandardFontLoadPrincipal(new gfxFontSrcPrincipal(
114 mDocument->NodePrincipal(), mDocument->PartitionedPrincipal())),
115 mResolveLazilyCreatedReadyPromise(false),
116 mStatus(FontFaceSetLoadStatus::Loaded),
117 mNonRuleFacesDirty(false),
118 mHasLoadingFontFaces(false),
119 mHasLoadingFontFacesIsDirty(false),
120 mDelayedLoadCheck(false),
121 mBypassCache(false),
122 mPrivateBrowsing(false) {
123 MOZ_ASSERT(mDocument, "We should get a valid document from the caller!");
125 // Record the state of the "bypass cache" flags from the docshell now,
126 // since we want to look at them from style worker threads, and we can
127 // only get to the docshell through a weak pointer (which is only
128 // possible on the main thread).
130 // In theory the load type of a docshell could change after the document
131 // is loaded, but handling that doesn't seem too important.
132 if (nsCOMPtr<nsIDocShell> docShell = mDocument->GetDocShell()) {
133 uint32_t loadType;
134 uint32_t flags;
135 if ((NS_SUCCEEDED(docShell->GetLoadType(&loadType)) &&
136 ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE)) ||
137 (NS_SUCCEEDED(docShell->GetDefaultLoadFlags(&flags)) &&
138 (flags & nsIRequest::LOAD_BYPASS_CACHE))) {
139 mBypassCache = true;
143 // Same for the "private browsing" flag.
144 if (nsCOMPtr<nsILoadContext> loadContext = mDocument->GetLoadContext()) {
145 mPrivateBrowsing = loadContext->UsePrivateBrowsing();
148 if (!mDocument->DidFireDOMContentLoaded()) {
149 mDocument->AddSystemEventListener(u"DOMContentLoaded"_ns, this, false,
150 false);
151 } else {
152 // In some cases we can't rely on CheckLoadingFinished being called from
153 // the refresh driver. For example, documents in display:none iframes.
154 // Or if the document has finished loading and painting at the time that
155 // script requests document.fonts and causes us to get here.
156 CheckLoadingFinished();
159 mDocument->CSSLoader()->AddObserver(this);
161 mUserFontSet = new UserFontSet(this);
164 FontFaceSet::~FontFaceSet() {
165 // Assert that we don't drop any FontFaceSet objects during a Servo traversal,
166 // since PostTraversalTask objects can hold raw pointers to FontFaceSets.
167 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
169 Disconnect();
172 JSObject* FontFaceSet::WrapObject(JSContext* aContext,
173 JS::Handle<JSObject*> aGivenProto) {
174 return FontFaceSet_Binding::Wrap(aContext, this, aGivenProto);
177 void FontFaceSet::Disconnect() {
178 RemoveDOMContentLoadedListener();
180 if (mDocument && mDocument->CSSLoader()) {
181 // We're null checking CSSLoader() since FontFaceSet::Disconnect() might be
182 // being called during unlink, at which time the loader amy already have
183 // been unlinked from the document.
184 mDocument->CSSLoader()->RemoveObserver(this);
187 for (auto it = mLoaders.Iter(); !it.Done(); it.Next()) {
188 it.Get()->GetKey()->Cancel();
191 mLoaders.Clear();
194 void FontFaceSet::RemoveDOMContentLoadedListener() {
195 if (mDocument) {
196 mDocument->RemoveSystemEventListener(u"DOMContentLoaded"_ns, this, false);
200 void FontFaceSet::ParseFontShorthandForMatching(
201 const nsACString& aFont, RefPtr<SharedFontList>& aFamilyList,
202 FontWeight& aWeight, FontStretch& aStretch, FontSlantStyle& aStyle,
203 ErrorResult& aRv) {
204 auto style = StyleComputedFontStyleDescriptor::Normal();
205 float stretch;
206 float weight;
208 RefPtr<URLExtraData> url = ServoCSSParser::GetURLExtraData(mDocument);
209 if (!ServoCSSParser::ParseFontShorthandForMatching(aFont, url, aFamilyList,
210 style, stretch, weight)) {
211 aRv.ThrowSyntaxError("Invalid font shorthand");
212 return;
215 switch (style.tag) {
216 case StyleComputedFontStyleDescriptor::Tag::Normal:
217 aStyle = FontSlantStyle::Normal();
218 break;
219 case StyleComputedFontStyleDescriptor::Tag::Italic:
220 aStyle = FontSlantStyle::Italic();
221 break;
222 case StyleComputedFontStyleDescriptor::Tag::Oblique:
223 MOZ_ASSERT(style.AsOblique()._0 == style.AsOblique()._1,
224 "We use ComputedFontStyleDescriptor just for convenience, "
225 "the two values should always match");
226 aStyle = FontSlantStyle::Oblique(style.AsOblique()._0);
227 break;
230 aWeight = FontWeight(weight);
231 aStretch = FontStretch::FromStyle(stretch);
234 static bool HasAnyCharacterInUnicodeRange(gfxUserFontEntry* aEntry,
235 const nsAString& aInput) {
236 const char16_t* p = aInput.Data();
237 const char16_t* end = p + aInput.Length();
239 while (p < end) {
240 uint32_t c = UTF16CharEnumerator::NextChar(&p, end);
241 if (aEntry->CharacterInUnicodeRange(c)) {
242 return true;
245 return false;
248 void FontFaceSet::FindMatchingFontFaces(const nsACString& aFont,
249 const nsAString& aText,
250 nsTArray<FontFace*>& aFontFaces,
251 ErrorResult& aRv) {
252 RefPtr<SharedFontList> familyList;
253 FontWeight weight;
254 FontStretch stretch;
255 FontSlantStyle italicStyle;
256 ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle,
257 aRv);
258 if (aRv.Failed()) {
259 return;
262 gfxFontStyle style;
263 style.style = italicStyle;
264 style.weight = weight;
265 style.stretch = stretch;
267 nsTArray<FontFaceRecord>* arrays[2];
268 arrays[0] = &mNonRuleFaces;
269 arrays[1] = &mRuleFaces;
271 // Set of FontFaces that we want to return.
272 nsTHashtable<nsPtrHashKey<FontFace>> matchingFaces;
274 for (const FontFamilyName& fontFamilyName : familyList->mNames) {
275 if (!fontFamilyName.IsNamed()) {
276 continue;
279 RefPtr<gfxFontFamily> family =
280 mUserFontSet->LookupFamily(nsAtomCString(fontFamilyName.mName));
282 if (!family) {
283 continue;
286 AutoTArray<gfxFontEntry*, 4> entries;
287 family->FindAllFontsForStyle(style, entries);
289 for (gfxFontEntry* e : entries) {
290 FontFace::Entry* entry = static_cast<FontFace::Entry*>(e);
291 if (HasAnyCharacterInUnicodeRange(entry, aText)) {
292 for (FontFace* f : entry->GetFontFaces()) {
293 matchingFaces.PutEntry(f);
299 // Add all FontFaces in matchingFaces to aFontFaces, in the order
300 // they appear in the FontFaceSet.
301 for (nsTArray<FontFaceRecord>* array : arrays) {
302 for (FontFaceRecord& record : *array) {
303 FontFace* f = record.mFontFace;
304 if (matchingFaces.Contains(f)) {
305 aFontFaces.AppendElement(f);
311 TimeStamp FontFaceSet::GetNavigationStartTimeStamp() {
312 TimeStamp navStart;
313 RefPtr<nsDOMNavigationTiming> timing(mDocument->GetNavigationTiming());
314 if (timing) {
315 navStart = timing->GetNavigationStartTimeStamp();
317 return navStart;
320 already_AddRefed<Promise> FontFaceSet::Load(JSContext* aCx,
321 const nsACString& aFont,
322 const nsAString& aText,
323 ErrorResult& aRv) {
324 FlushUserFontSet();
326 nsTArray<RefPtr<Promise>> promises;
328 nsTArray<FontFace*> faces;
329 FindMatchingFontFaces(aFont, aText, faces, aRv);
330 if (aRv.Failed()) {
331 return nullptr;
334 for (FontFace* f : faces) {
335 RefPtr<Promise> promise = f->Load(aRv);
336 if (aRv.Failed()) {
337 return nullptr;
339 if (!promises.AppendElement(promise, fallible)) {
340 aRv.Throw(NS_ERROR_FAILURE);
341 return nullptr;
345 return Promise::All(aCx, promises, aRv);
348 bool FontFaceSet::Check(const nsACString& aFont, const nsAString& aText,
349 ErrorResult& aRv) {
350 FlushUserFontSet();
352 nsTArray<FontFace*> faces;
353 FindMatchingFontFaces(aFont, aText, faces, aRv);
354 if (aRv.Failed()) {
355 return false;
358 for (FontFace* f : faces) {
359 if (f->Status() != FontFaceLoadStatus::Loaded) {
360 return false;
364 return true;
367 bool FontFaceSet::ReadyPromiseIsPending() const {
368 return mReady ? mReady->State() == Promise::PromiseState::Pending
369 : !mResolveLazilyCreatedReadyPromise;
372 Promise* FontFaceSet::GetReady(ErrorResult& aRv) {
373 MOZ_ASSERT(NS_IsMainThread());
375 // There may be outstanding style changes that will trigger the loading of
376 // new fonts. We need to flush layout to initiate any such loads so that
377 // if mReady is currently resolved we replace it with a new pending Promise.
378 // (That replacement will happen under this flush call.)
379 if (!ReadyPromiseIsPending() && mDocument) {
380 mDocument->FlushPendingNotifications(FlushType::Layout);
383 if (!mReady) {
384 nsCOMPtr<nsIGlobalObject> global = GetParentObject();
385 mReady = Promise::Create(global, aRv);
386 if (!mReady) {
387 aRv.Throw(NS_ERROR_FAILURE);
388 return nullptr;
390 if (mResolveLazilyCreatedReadyPromise) {
391 mReady->MaybeResolve(this);
392 mResolveLazilyCreatedReadyPromise = false;
396 return mReady;
399 FontFaceSetLoadStatus FontFaceSet::Status() {
400 FlushUserFontSet();
401 return mStatus;
404 #ifdef DEBUG
405 bool FontFaceSet::HasRuleFontFace(FontFace* aFontFace) {
406 for (size_t i = 0; i < mRuleFaces.Length(); i++) {
407 if (mRuleFaces[i].mFontFace == aFontFace) {
408 return true;
411 return false;
413 #endif
415 void FontFaceSet::Add(FontFace& aFontFace, ErrorResult& aRv) {
416 FlushUserFontSet();
418 if (aFontFace.IsInFontFaceSet(this)) {
419 return;
422 if (aFontFace.HasRule()) {
423 aRv.ThrowInvalidModificationError(
424 "Can't add face to FontFaceSet that comes from an @font-face rule");
425 return;
428 aFontFace.AddFontFaceSet(this);
430 #ifdef DEBUG
431 for (const FontFaceRecord& rec : mNonRuleFaces) {
432 MOZ_ASSERT(rec.mFontFace != &aFontFace,
433 "FontFace should not occur in mNonRuleFaces twice");
435 #endif
437 FontFaceRecord* rec = mNonRuleFaces.AppendElement();
438 rec->mFontFace = &aFontFace;
439 rec->mOrigin = Nothing();
440 rec->mLoadEventShouldFire =
441 aFontFace.Status() == FontFaceLoadStatus::Unloaded ||
442 aFontFace.Status() == FontFaceLoadStatus::Loading;
444 mNonRuleFacesDirty = true;
445 MarkUserFontSetDirty();
446 mHasLoadingFontFacesIsDirty = true;
447 CheckLoadingStarted();
448 RefPtr<dom::Document> clonedDoc = mDocument->GetLatestStaticClone();
449 if (clonedDoc) {
450 // The document is printing, copy the font to the static clone as well.
451 nsCOMPtr<nsIPrincipal> principal = mDocument->GetPrincipal();
452 if (principal->IsSystemPrincipal() || nsContentUtils::IsPDFJS(principal)) {
453 ErrorResult rv;
454 clonedDoc->Fonts()->Add(aFontFace, rv);
455 MOZ_ASSERT(!rv.Failed());
460 void FontFaceSet::Clear() {
461 FlushUserFontSet();
463 if (mNonRuleFaces.IsEmpty()) {
464 return;
467 for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
468 FontFace* f = mNonRuleFaces[i].mFontFace;
469 f->RemoveFontFaceSet(this);
472 mNonRuleFaces.Clear();
473 mNonRuleFacesDirty = true;
474 MarkUserFontSetDirty();
475 mHasLoadingFontFacesIsDirty = true;
476 CheckLoadingFinished();
479 bool FontFaceSet::Delete(FontFace& aFontFace) {
480 FlushUserFontSet();
482 if (aFontFace.HasRule()) {
483 return false;
486 bool removed = false;
487 for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
488 if (mNonRuleFaces[i].mFontFace == &aFontFace) {
489 mNonRuleFaces.RemoveElementAt(i);
490 removed = true;
491 break;
494 if (!removed) {
495 return false;
498 aFontFace.RemoveFontFaceSet(this);
500 mNonRuleFacesDirty = true;
501 MarkUserFontSetDirty();
502 mHasLoadingFontFacesIsDirty = true;
503 CheckLoadingFinished();
504 return true;
507 bool FontFaceSet::HasAvailableFontFace(FontFace* aFontFace) {
508 return aFontFace->IsInFontFaceSet(this);
511 bool FontFaceSet::Has(FontFace& aFontFace) {
512 FlushUserFontSet();
514 return HasAvailableFontFace(&aFontFace);
517 FontFace* FontFaceSet::GetFontFaceAt(uint32_t aIndex) {
518 FlushUserFontSet();
520 if (aIndex < mRuleFaces.Length()) {
521 return mRuleFaces[aIndex].mFontFace;
524 aIndex -= mRuleFaces.Length();
525 if (aIndex < mNonRuleFaces.Length()) {
526 return mNonRuleFaces[aIndex].mFontFace;
529 return nullptr;
532 uint32_t FontFaceSet::Size() {
533 FlushUserFontSet();
535 // Web IDL objects can only expose array index properties up to INT32_MAX.
537 size_t total = mRuleFaces.Length() + mNonRuleFaces.Length();
538 return std::min<size_t>(total, INT32_MAX);
541 already_AddRefed<FontFaceSetIterator> FontFaceSet::Entries() {
542 RefPtr<FontFaceSetIterator> it = new FontFaceSetIterator(this, true);
543 return it.forget();
546 already_AddRefed<FontFaceSetIterator> FontFaceSet::Values() {
547 RefPtr<FontFaceSetIterator> it = new FontFaceSetIterator(this, false);
548 return it.forget();
551 void FontFaceSet::ForEach(JSContext* aCx, FontFaceSetForEachCallback& aCallback,
552 JS::Handle<JS::Value> aThisArg, ErrorResult& aRv) {
553 JS::Rooted<JS::Value> thisArg(aCx, aThisArg);
554 for (size_t i = 0; i < Size(); i++) {
555 RefPtr<FontFace> face = GetFontFaceAt(i);
556 aCallback.Call(thisArg, *face, *face, *this, aRv);
557 if (aRv.Failed()) {
558 return;
563 void FontFaceSet::RemoveLoader(nsFontFaceLoader* aLoader) {
564 mLoaders.RemoveEntry(aLoader);
567 nsresult FontFaceSet::StartLoad(gfxUserFontEntry* aUserFontEntry,
568 const gfxFontFaceSrc* aFontFaceSrc) {
569 nsresult rv;
571 nsCOMPtr<nsIStreamLoader> streamLoader;
572 RefPtr<nsFontFaceLoader> fontLoader;
574 auto preloadKey =
575 PreloadHashKey::CreateAsFont(aFontFaceSrc->mURI->get(), CORS_ANONYMOUS);
576 RefPtr<PreloaderBase> preload =
577 mDocument->Preloads().LookupPreload(preloadKey);
579 if (preload) {
580 fontLoader = new nsFontFaceLoader(aUserFontEntry, aFontFaceSrc->mURI->get(),
581 this, preload->Channel());
583 rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader,
584 fontLoader);
585 NS_ENSURE_SUCCESS(rv, rv);
587 rv = preload->AsyncConsume(streamLoader);
589 // We don't want this to hang around regardless of the result, there will be
590 // no coalescing of later found <link preload> tags for fonts.
591 preload->RemoveSelf(mDocument);
592 } else {
593 // No preload found, open a channel.
594 rv = NS_ERROR_FAILURE;
597 nsCOMPtr<nsILoadGroup> loadGroup(mDocument->GetDocumentLoadGroup());
598 if (NS_FAILED(rv)) {
599 nsCOMPtr<nsIChannel> channel;
600 rv = FontPreloader::BuildChannel(
601 getter_AddRefs(channel), aFontFaceSrc->mURI->get(), CORS_ANONYMOUS,
602 dom::ReferrerPolicy::_empty /* not used */, aUserFontEntry,
603 aFontFaceSrc, mDocument, loadGroup, nullptr, false);
604 NS_ENSURE_SUCCESS(rv, rv);
606 fontLoader = new nsFontFaceLoader(aUserFontEntry, aFontFaceSrc->mURI->get(),
607 this, channel);
609 if (LOG_ENABLED()) {
610 nsCOMPtr<nsIURI> referrer =
611 aFontFaceSrc->mReferrerInfo
612 ? aFontFaceSrc->mReferrerInfo->GetOriginalReferrer()
613 : nullptr;
614 LOG((
615 "userfonts (%p) download start - font uri: (%s) referrer uri: (%s)\n",
616 fontLoader.get(), aFontFaceSrc->mURI->GetSpecOrDefault().get(),
617 referrer ? referrer->GetSpecOrDefault().get() : ""));
620 rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader,
621 fontLoader);
622 NS_ENSURE_SUCCESS(rv, rv);
624 rv = channel->AsyncOpen(streamLoader);
625 if (NS_FAILED(rv)) {
626 fontLoader->DropChannel(); // explicitly need to break ref cycle
630 mLoaders.PutEntry(fontLoader);
632 net::PredictorLearn(aFontFaceSrc->mURI->get(), mDocument->GetDocumentURI(),
633 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadGroup);
635 if (NS_SUCCEEDED(rv)) {
636 fontLoader->StartedLoading(streamLoader);
637 // let the font entry remember the loader, in case we need to cancel it
638 aUserFontEntry->SetLoader(fontLoader);
641 return rv;
644 bool FontFaceSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules) {
645 MOZ_ASSERT(mUserFontSet);
647 // If there was a change to the mNonRuleFaces array, then there could
648 // have been a modification to the user font set.
649 bool modified = mNonRuleFacesDirty;
650 mNonRuleFacesDirty = false;
652 // reuse existing FontFace objects mapped to rules already
653 nsTHashMap<nsPtrHashKey<RawServoFontFaceRule>, FontFace*> ruleFaceMap;
654 for (size_t i = 0, i_end = mRuleFaces.Length(); i < i_end; ++i) {
655 FontFace* f = mRuleFaces[i].mFontFace;
656 if (!f) {
657 continue;
659 ruleFaceMap.InsertOrUpdate(f->GetRule(), f);
662 // The @font-face rules that make up the user font set have changed,
663 // so we need to update the set. However, we want to preserve existing
664 // font entries wherever possible, so that we don't discard and then
665 // re-download resources in the (common) case where at least some of the
666 // same rules are still present.
668 nsTArray<FontFaceRecord> oldRecords = std::move(mRuleFaces);
670 // Remove faces from the font family records; we need to re-insert them
671 // because we might end up with faces in a different order even if they're
672 // the same font entries as before. (The order can affect font selection
673 // where multiple faces match the requested style, perhaps with overlapping
674 // unicode-range coverage.)
675 for (auto it = mUserFontSet->mFontFamilies.Iter(); !it.Done(); it.Next()) {
676 it.Data()->DetachFontEntries();
679 // Sometimes aRules has duplicate @font-face rules in it; we should make
680 // that not happen, but in the meantime, don't try to insert the same
681 // FontFace object more than once into mRuleFaces. We track which
682 // ones we've handled in this table.
683 nsTHashtable<nsPtrHashKey<RawServoFontFaceRule>> handledRules;
685 for (size_t i = 0, i_end = aRules.Length(); i < i_end; ++i) {
686 // Insert each FontFace objects for each rule into our list, migrating old
687 // font entries if possible rather than creating new ones; set modified to
688 // true if we detect that rule ordering has changed, or if a new entry is
689 // created.
690 RawServoFontFaceRule* rule = aRules[i].mRule;
691 if (!handledRules.EnsureInserted(rule)) {
692 // rule was already present in the hashtable
693 continue;
695 RefPtr<FontFace> f = ruleFaceMap.Get(rule);
696 if (!f.get()) {
697 f = FontFace::CreateForRule(GetParentObject(), this, rule);
699 InsertRuleFontFace(f, aRules[i].mOrigin, oldRecords, modified);
702 for (size_t i = 0, i_end = mNonRuleFaces.Length(); i < i_end; ++i) {
703 // Do the same for the non rule backed FontFace objects.
704 InsertNonRuleFontFace(mNonRuleFaces[i].mFontFace, modified);
707 // Remove any residual families that have no font entries (i.e., they were
708 // not defined at all by the updated set of @font-face rules).
709 for (auto it = mUserFontSet->mFontFamilies.Iter(); !it.Done(); it.Next()) {
710 if (it.Data()->GetFontList().IsEmpty()) {
711 it.Remove();
715 // If any FontFace objects for rules are left in the old list, note that the
716 // set has changed (even if the new set was built entirely by migrating old
717 // font entries).
718 if (oldRecords.Length() > 0) {
719 modified = true;
720 // Any in-progress loaders for obsolete rules should be cancelled,
721 // as the resource being downloaded will no longer be required.
722 // We need to explicitly remove any loaders here, otherwise the loaders
723 // will keep their "orphaned" font entries alive until they complete,
724 // even after the oldRules array is deleted.
726 // XXX Now that it is possible for the author to hold on to a rule backed
727 // FontFace object, we shouldn't cancel loading here; instead we should do
728 // it when the FontFace is GCed, if we can detect that.
729 size_t count = oldRecords.Length();
730 for (size_t i = 0; i < count; ++i) {
731 RefPtr<FontFace> f = oldRecords[i].mFontFace;
732 gfxUserFontEntry* userFontEntry = f->GetUserFontEntry();
733 if (userFontEntry) {
734 nsFontFaceLoader* loader = userFontEntry->GetLoader();
735 if (loader) {
736 loader->Cancel();
737 RemoveLoader(loader);
741 // Any left over FontFace objects should also cease being rule backed.
742 f->DisconnectFromRule();
746 if (modified) {
747 IncrementGeneration(true);
748 mHasLoadingFontFacesIsDirty = true;
749 CheckLoadingStarted();
750 CheckLoadingFinished();
753 // if local rules needed to be rebuilt, they have been rebuilt at this point
754 if (mUserFontSet->mRebuildLocalRules) {
755 mUserFontSet->mLocalRulesUsed = false;
756 mUserFontSet->mRebuildLocalRules = false;
759 if (LOG_ENABLED() && !mRuleFaces.IsEmpty()) {
760 LOG(("userfonts (%p) userfont rules update (%s) rule count: %d",
761 mUserFontSet.get(), (modified ? "modified" : "not modified"),
762 (int)(mRuleFaces.Length())));
765 return modified;
768 void FontFaceSet::IncrementGeneration(bool aIsRebuild) {
769 MOZ_ASSERT(mUserFontSet);
770 mUserFontSet->IncrementGeneration(aIsRebuild);
773 void FontFaceSet::InsertNonRuleFontFace(FontFace* aFontFace,
774 bool& aFontSetModified) {
775 nsAtom* fontFamily = aFontFace->GetFamilyName();
776 if (!fontFamily) {
777 // If there is no family name, this rule cannot contribute a
778 // usable font, so there is no point in processing it further.
779 return;
782 nsAtomCString family(fontFamily);
784 // Just create a new font entry if we haven't got one already.
785 if (!aFontFace->GetUserFontEntry()) {
786 // XXX Should we be checking mUserFontSet->mLocalRulesUsed like
787 // InsertRuleFontFace does?
788 RefPtr<gfxUserFontEntry> entry = FindOrCreateUserFontEntryFromFontFace(
789 family, aFontFace, StyleOrigin::Author);
790 if (!entry) {
791 return;
793 aFontFace->SetUserFontEntry(entry);
796 aFontSetModified = true;
797 mUserFontSet->AddUserFontEntry(family, aFontFace->GetUserFontEntry());
800 void FontFaceSet::InsertRuleFontFace(FontFace* aFontFace,
801 StyleOrigin aSheetType,
802 nsTArray<FontFaceRecord>& aOldRecords,
803 bool& aFontSetModified) {
804 nsAtom* fontFamily = aFontFace->GetFamilyName();
805 if (!fontFamily) {
806 // If there is no family name, this rule cannot contribute a
807 // usable font, so there is no point in processing it further.
808 return;
811 bool remove = false;
812 size_t removeIndex;
814 nsAtomCString family(fontFamily);
816 // This is a rule backed FontFace. First, we check in aOldRecords; if
817 // the FontFace for the rule exists there, just move it to the new record
818 // list, and put the entry into the appropriate family.
819 for (size_t i = 0; i < aOldRecords.Length(); ++i) {
820 FontFaceRecord& rec = aOldRecords[i];
822 if (rec.mFontFace == aFontFace && rec.mOrigin == Some(aSheetType)) {
823 // if local rules were used, don't use the old font entry
824 // for rules containing src local usage
825 if (mUserFontSet->mLocalRulesUsed && mUserFontSet->mRebuildLocalRules) {
826 if (aFontFace->HasLocalSrc()) {
827 // Remove the old record, but wait to see if we successfully create a
828 // new user font entry below.
829 remove = true;
830 removeIndex = i;
831 break;
835 gfxUserFontEntry* entry = rec.mFontFace->GetUserFontEntry();
836 MOZ_ASSERT(entry, "FontFace should have a gfxUserFontEntry by now");
838 mUserFontSet->AddUserFontEntry(family, entry);
840 MOZ_ASSERT(!HasRuleFontFace(rec.mFontFace),
841 "FontFace should not occur in mRuleFaces twice");
843 mRuleFaces.AppendElement(rec);
844 aOldRecords.RemoveElementAt(i);
845 // note the set has been modified if an old rule was skipped to find
846 // this one - something has been dropped, or ordering changed
847 if (i > 0) {
848 aFontSetModified = true;
850 return;
854 // this is a new rule:
855 RefPtr<gfxUserFontEntry> entry =
856 FindOrCreateUserFontEntryFromFontFace(family, aFontFace, aSheetType);
858 if (!entry) {
859 return;
862 if (remove) {
863 // Although we broke out of the aOldRecords loop above, since we found
864 // src local usage, and we're not using the old user font entry, we still
865 // are adding a record to mRuleFaces with the same FontFace object.
866 // Remove the old record so that we don't have the same FontFace listed
867 // in both mRuleFaces and oldRecords, which would cause us to call
868 // DisconnectFromRule on a FontFace that should still be rule backed.
869 aOldRecords.RemoveElementAt(removeIndex);
872 FontFaceRecord rec;
873 rec.mFontFace = aFontFace;
874 rec.mOrigin = Some(aSheetType);
875 rec.mLoadEventShouldFire =
876 aFontFace->Status() == FontFaceLoadStatus::Unloaded ||
877 aFontFace->Status() == FontFaceLoadStatus::Loading;
879 aFontFace->SetUserFontEntry(entry);
881 MOZ_ASSERT(!HasRuleFontFace(aFontFace),
882 "FontFace should not occur in mRuleFaces twice");
884 mRuleFaces.AppendElement(rec);
886 // this was a new rule and font entry, so note that the set was modified
887 aFontSetModified = true;
889 // Add the entry to the end of the list. If an existing userfont entry was
890 // returned by FindOrCreateUserFontEntryFromFontFace that was already stored
891 // on the family, gfxUserFontFamily::AddFontEntry(), which AddUserFontEntry
892 // calls, will automatically remove the earlier occurrence of the same
893 // userfont entry.
894 mUserFontSet->AddUserFontEntry(family, entry);
897 /* static */
898 already_AddRefed<gfxUserFontEntry>
899 FontFaceSet::FindOrCreateUserFontEntryFromFontFace(FontFace* aFontFace) {
900 nsAtom* fontFamily = aFontFace->GetFamilyName();
901 if (!fontFamily) {
902 // If there is no family name, this rule cannot contribute a
903 // usable font, so there is no point in processing it further.
904 return nullptr;
907 return FindOrCreateUserFontEntryFromFontFace(nsAtomCString(fontFamily),
908 aFontFace, StyleOrigin::Author);
911 static WeightRange GetWeightRangeForDescriptor(
912 const Maybe<StyleComputedFontWeightRange>& aVal,
913 gfxFontEntry::RangeFlags& aRangeFlags) {
914 if (!aVal) {
915 aRangeFlags |= gfxFontEntry::RangeFlags::eAutoWeight;
916 return WeightRange(FontWeight::Normal());
918 return WeightRange(FontWeight(aVal->_0), FontWeight(aVal->_1));
921 static SlantStyleRange GetStyleRangeForDescriptor(
922 const Maybe<StyleComputedFontStyleDescriptor>& aVal,
923 gfxFontEntry::RangeFlags& aRangeFlags) {
924 if (!aVal) {
925 aRangeFlags |= gfxFontEntry::RangeFlags::eAutoSlantStyle;
926 return SlantStyleRange(FontSlantStyle::Normal());
928 auto& val = *aVal;
929 switch (val.tag) {
930 case StyleComputedFontStyleDescriptor::Tag::Normal:
931 return SlantStyleRange(FontSlantStyle::Normal());
932 case StyleComputedFontStyleDescriptor::Tag::Italic:
933 return SlantStyleRange(FontSlantStyle::Italic());
934 case StyleComputedFontStyleDescriptor::Tag::Oblique:
935 return SlantStyleRange(FontSlantStyle::Oblique(val.AsOblique()._0),
936 FontSlantStyle::Oblique(val.AsOblique()._1));
938 MOZ_ASSERT_UNREACHABLE("How?");
939 return SlantStyleRange(FontSlantStyle::Normal());
942 static StretchRange GetStretchRangeForDescriptor(
943 const Maybe<StyleComputedFontStretchRange>& aVal,
944 gfxFontEntry::RangeFlags& aRangeFlags) {
945 if (!aVal) {
946 aRangeFlags |= gfxFontEntry::RangeFlags::eAutoStretch;
947 return StretchRange(FontStretch::Normal());
949 return StretchRange(FontStretch::FromStyle(aVal->_0),
950 FontStretch::FromStyle(aVal->_1));
953 // TODO(emilio): Should this take an nsAtom* aFamilyName instead?
955 // All callers have one handy.
956 /* static */
957 already_AddRefed<gfxUserFontEntry>
958 FontFaceSet::FindOrCreateUserFontEntryFromFontFace(
959 const nsACString& aFamilyName, FontFace* aFontFace, StyleOrigin aOrigin) {
960 FontFaceSet* set = aFontFace->GetPrimaryFontFaceSet();
962 uint32_t languageOverride = NO_FONT_LANGUAGE_OVERRIDE;
963 StyleFontDisplay fontDisplay = StyleFontDisplay::Auto;
965 gfxFontEntry::RangeFlags rangeFlags = gfxFontEntry::RangeFlags::eNoFlags;
967 // set up weight
968 WeightRange weight =
969 GetWeightRangeForDescriptor(aFontFace->GetFontWeight(), rangeFlags);
971 // set up stretch
972 StretchRange stretch =
973 GetStretchRangeForDescriptor(aFontFace->GetFontStretch(), rangeFlags);
975 // set up font style
976 SlantStyleRange italicStyle =
977 GetStyleRangeForDescriptor(aFontFace->GetFontStyle(), rangeFlags);
979 // set up font display
980 if (Maybe<StyleFontDisplay> display = aFontFace->GetFontDisplay()) {
981 fontDisplay = *display;
984 // set up font features
985 nsTArray<gfxFontFeature> featureSettings;
986 aFontFace->GetFontFeatureSettings(featureSettings);
988 // set up font variations
989 nsTArray<gfxFontVariation> variationSettings;
990 aFontFace->GetFontVariationSettings(variationSettings);
992 // set up font language override
993 if (Maybe<StyleFontLanguageOverride> descriptor =
994 aFontFace->GetFontLanguageOverride()) {
995 languageOverride = descriptor->_0;
998 // set up unicode-range
999 gfxCharacterMap* unicodeRanges = aFontFace->GetUnicodeRangeAsCharacterMap();
1001 RefPtr<gfxUserFontEntry> existingEntry = aFontFace->GetUserFontEntry();
1002 if (existingEntry) {
1003 // aFontFace already has a user font entry, so we update its attributes
1004 // rather than creating a new one.
1005 existingEntry->UpdateAttributes(
1006 weight, stretch, italicStyle, featureSettings, variationSettings,
1007 languageOverride, unicodeRanges, fontDisplay, rangeFlags);
1008 // If the family name has changed, remove the entry from its current family
1009 // and clear the mFamilyName field so it can be reset when added to a new
1010 // family.
1011 if (!existingEntry->mFamilyName.IsEmpty() &&
1012 existingEntry->mFamilyName != aFamilyName) {
1013 gfxUserFontFamily* family =
1014 set->GetUserFontSet()->LookupFamily(existingEntry->mFamilyName);
1015 if (family) {
1016 family->RemoveFontEntry(existingEntry);
1018 existingEntry->mFamilyName.Truncate(0);
1020 return existingEntry.forget();
1023 // set up src array
1024 nsTArray<gfxFontFaceSrc> srcArray;
1026 if (aFontFace->HasFontData()) {
1027 gfxFontFaceSrc* face = srcArray.AppendElement();
1028 if (!face) {
1029 return nullptr;
1032 face->mSourceType = gfxFontFaceSrc::eSourceType_Buffer;
1033 face->mBuffer = aFontFace->CreateBufferSource();
1034 } else {
1035 AutoTArray<StyleFontFaceSourceListComponent, 8> sourceListComponents;
1036 aFontFace->GetSources(sourceListComponents);
1037 size_t len = sourceListComponents.Length();
1038 for (size_t i = 0; i < len; ++i) {
1039 gfxFontFaceSrc* face = srcArray.AppendElement();
1040 const auto& component = sourceListComponents[i];
1041 switch (component.tag) {
1042 case StyleFontFaceSourceListComponent::Tag::Local: {
1043 nsAtom* atom = component.AsLocal();
1044 face->mLocalName.Append(nsAtomCString(atom));
1045 face->mSourceType = gfxFontFaceSrc::eSourceType_Local;
1046 face->mURI = nullptr;
1047 face->mFormatFlags = 0;
1048 break;
1050 case StyleFontFaceSourceListComponent::Tag::Url: {
1051 face->mSourceType = gfxFontFaceSrc::eSourceType_URL;
1052 const StyleCssUrl* url = component.AsUrl();
1053 nsIURI* uri = url->GetURI();
1054 face->mURI = uri ? new gfxFontSrcURI(uri) : nullptr;
1055 const URLExtraData& extraData = url->ExtraData();
1056 face->mReferrerInfo = extraData.ReferrerInfo();
1057 face->mOriginPrincipal = new gfxFontSrcPrincipal(
1058 extraData.Principal(), extraData.Principal());
1060 // agent and user stylesheets are treated slightly differently,
1061 // the same-site origin check and access control headers are
1062 // enforced against the sheet principal rather than the document
1063 // principal to allow user stylesheets to include @font-face rules
1064 face->mUseOriginPrincipal =
1065 aOrigin == StyleOrigin::User || aOrigin == StyleOrigin::UserAgent;
1067 face->mLocalName.Truncate();
1068 face->mFormatFlags = 0;
1070 while (i + 1 < len) {
1071 const auto& maybeFontFormat = sourceListComponents[i + 1];
1072 if (maybeFontFormat.tag !=
1073 StyleFontFaceSourceListComponent::Tag::FormatHint) {
1074 break;
1077 nsDependentCSubstring valueString(
1078 reinterpret_cast<const char*>(
1079 maybeFontFormat.format_hint.utf8_bytes),
1080 maybeFontFormat.format_hint.length);
1082 if (valueString.LowerCaseEqualsASCII("woff")) {
1083 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_WOFF;
1084 } else if (valueString.LowerCaseEqualsASCII("woff2")) {
1085 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_WOFF2;
1086 } else if (valueString.LowerCaseEqualsASCII("opentype")) {
1087 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_OPENTYPE;
1088 } else if (valueString.LowerCaseEqualsASCII("truetype")) {
1089 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_TRUETYPE;
1090 } else if (valueString.LowerCaseEqualsASCII("truetype-aat")) {
1091 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT;
1092 } else if (valueString.LowerCaseEqualsASCII("embedded-opentype")) {
1093 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_EOT;
1094 } else if (valueString.LowerCaseEqualsASCII("svg")) {
1095 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_SVG;
1096 } else if (StaticPrefs::layout_css_font_variations_enabled() &&
1097 valueString.LowerCaseEqualsASCII("woff-variations")) {
1098 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_WOFF_VARIATIONS;
1099 } else if (StaticPrefs::layout_css_font_variations_enabled() &&
1100 valueString.LowerCaseEqualsASCII("woff2-variations")) {
1101 face->mFormatFlags |=
1102 gfxUserFontSet::FLAG_FORMAT_WOFF2_VARIATIONS;
1103 } else if (StaticPrefs::layout_css_font_variations_enabled() &&
1104 valueString.LowerCaseEqualsASCII(
1105 "opentype-variations")) {
1106 face->mFormatFlags |=
1107 gfxUserFontSet::FLAG_FORMAT_OPENTYPE_VARIATIONS;
1108 } else if (StaticPrefs::layout_css_font_variations_enabled() &&
1109 valueString.LowerCaseEqualsASCII(
1110 "truetype-variations")) {
1111 face->mFormatFlags |=
1112 gfxUserFontSet::FLAG_FORMAT_TRUETYPE_VARIATIONS;
1113 } else {
1114 // unknown format specified, mark to distinguish from the
1115 // case where no format hints are specified
1116 face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_UNKNOWN;
1118 i++;
1120 if (!face->mURI) {
1121 // if URI not valid, omit from src array
1122 srcArray.RemoveLastElement();
1123 NS_WARNING("null url in @font-face rule");
1124 continue;
1126 break;
1128 case StyleFontFaceSourceListComponent::Tag::FormatHint:
1129 MOZ_ASSERT_UNREACHABLE(
1130 "Should always come after a URL source, and be consumed already");
1131 break;
1136 if (srcArray.IsEmpty()) {
1137 return nullptr;
1140 RefPtr<gfxUserFontEntry> entry = set->mUserFontSet->FindOrCreateUserFontEntry(
1141 aFamilyName, srcArray, weight, stretch, italicStyle, featureSettings,
1142 variationSettings, languageOverride, unicodeRanges, fontDisplay,
1143 rangeFlags);
1145 return entry.forget();
1148 RawServoFontFaceRule* FontFaceSet::FindRuleForEntry(gfxFontEntry* aFontEntry) {
1149 NS_ASSERTION(!aFontEntry->mIsUserFontContainer, "only platform font entries");
1150 for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
1151 FontFace* f = mRuleFaces[i].mFontFace;
1152 gfxUserFontEntry* entry = f->GetUserFontEntry();
1153 if (entry && entry->GetPlatformFontEntry() == aFontEntry) {
1154 return f->GetRule();
1157 return nullptr;
1160 RawServoFontFaceRule* FontFaceSet::FindRuleForUserFontEntry(
1161 gfxUserFontEntry* aUserFontEntry) {
1162 for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
1163 FontFace* f = mRuleFaces[i].mFontFace;
1164 if (f->GetUserFontEntry() == aUserFontEntry) {
1165 return f->GetRule();
1168 return nullptr;
1171 nsresult FontFaceSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
1172 const char* aMessage, uint32_t aFlags,
1173 nsresult aStatus) {
1174 MOZ_ASSERT(NS_IsMainThread() ||
1175 ServoStyleSet::IsCurrentThreadInServoTraversal());
1177 nsCOMPtr<nsIConsoleService> console(
1178 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
1179 if (!console) {
1180 return NS_ERROR_NOT_AVAILABLE;
1183 nsAutoCString familyName;
1184 nsAutoCString fontURI;
1185 aUserFontEntry->GetFamilyNameAndURIForLogging(familyName, fontURI);
1187 nsAutoCString weightString;
1188 aUserFontEntry->Weight().ToString(weightString);
1189 nsAutoCString stretchString;
1190 aUserFontEntry->Stretch().ToString(stretchString);
1191 nsPrintfCString message(
1192 "downloadable font: %s "
1193 "(font-family: \"%s\" style:%s weight:%s stretch:%s src index:%d)",
1194 aMessage, familyName.get(),
1195 aUserFontEntry->IsItalic() ? "italic" : "normal", // XXX todo: oblique?
1196 weightString.get(), stretchString.get(), aUserFontEntry->GetSrcIndex());
1198 if (NS_FAILED(aStatus)) {
1199 message.AppendLiteral(": ");
1200 switch (aStatus) {
1201 case NS_ERROR_DOM_BAD_URI:
1202 message.AppendLiteral("bad URI or cross-site access not allowed");
1203 break;
1204 case NS_ERROR_CONTENT_BLOCKED:
1205 message.AppendLiteral("content blocked");
1206 break;
1207 default:
1208 message.AppendLiteral("status=");
1209 message.AppendInt(static_cast<uint32_t>(aStatus));
1210 break;
1213 message.AppendLiteral(" source: ");
1214 message.Append(fontURI);
1216 LOG(("userfonts (%p) %s", mUserFontSet.get(), message.get()));
1218 // try to give the user an indication of where the rule came from
1219 RawServoFontFaceRule* rule = FindRuleForUserFontEntry(aUserFontEntry);
1220 nsString href;
1221 nsAutoCString text;
1222 uint32_t line = 0;
1223 uint32_t column = 0;
1224 if (rule) {
1225 Servo_FontFaceRule_GetCssText(rule, &text);
1226 Servo_FontFaceRule_GetSourceLocation(rule, &line, &column);
1227 // FIXME We need to figure out an approach to get the style sheet
1228 // of this raw rule. See bug 1450903.
1229 #if 0
1230 StyleSheet* sheet = rule->GetStyleSheet();
1231 // if the style sheet is removed while the font is loading can be null
1232 if (sheet) {
1233 nsCString spec = sheet->GetSheetURI()->GetSpecOrDefault();
1234 CopyUTF8toUTF16(spec, href);
1235 } else {
1236 NS_WARNING("null parent stylesheet for @font-face rule");
1237 href.AssignLiteral("unknown");
1239 #endif
1240 // Leave href empty if we don't know how to get the correct sheet.
1243 nsresult rv;
1244 nsCOMPtr<nsIScriptError> scriptError =
1245 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
1246 NS_ENSURE_SUCCESS(rv, rv);
1248 uint64_t innerWindowID = mDocument->InnerWindowID();
1249 rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(message),
1250 href, // file
1251 NS_ConvertUTF8toUTF16(text), // src line
1252 line, column,
1253 aFlags, // flags
1254 "CSS Loader", // category (make separate?)
1255 innerWindowID);
1256 if (NS_SUCCEEDED(rv)) {
1257 console->LogMessage(scriptError);
1260 return NS_OK;
1263 void FontFaceSet::CacheFontLoadability() {
1264 if (!mUserFontSet) {
1265 return;
1268 // TODO(emilio): We could do it a bit more incrementally maybe?
1269 for (auto iter = mUserFontSet->mFontFamilies.Iter(); !iter.Done();
1270 iter.Next()) {
1271 for (const gfxFontEntry* entry : iter.Data()->GetFontList()) {
1272 if (!entry->mIsUserFontContainer) {
1273 continue;
1276 const auto& sourceList =
1277 static_cast<const gfxUserFontEntry*>(entry)->SourceList();
1278 for (const gfxFontFaceSrc& src : sourceList) {
1279 if (src.mSourceType != gfxFontFaceSrc::eSourceType_URL) {
1280 continue;
1282 mAllowedFontLoads.LookupOrInsertWith(
1283 &src, [&] { return IsFontLoadAllowed(src); });
1289 bool FontFaceSet::IsFontLoadAllowed(const gfxFontFaceSrc& aSrc) {
1290 MOZ_ASSERT(aSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL);
1292 if (ServoStyleSet::IsInServoTraversal()) {
1293 auto entry = mAllowedFontLoads.Lookup(&aSrc);
1294 MOZ_DIAGNOSTIC_ASSERT(entry, "Missed an update?");
1295 return entry ? *entry : false;
1298 MOZ_ASSERT(NS_IsMainThread());
1300 if (!mUserFontSet) {
1301 return false;
1304 gfxFontSrcPrincipal* gfxPrincipal = aSrc.mURI->InheritsSecurityContext()
1305 ? nullptr
1306 : aSrc.LoadPrincipal(*mUserFontSet);
1308 nsIPrincipal* principal =
1309 gfxPrincipal ? gfxPrincipal->NodePrincipal() : nullptr;
1311 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
1312 mDocument->NodePrincipal(), // loading principal
1313 principal, // triggering principal
1314 mDocument, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
1315 nsIContentPolicy::TYPE_FONT);
1317 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
1318 nsresult rv = NS_CheckContentLoadPolicy(aSrc.mURI->get(), secCheckLoadInfo,
1319 ""_ns, // mime type
1320 &shouldLoad,
1321 nsContentUtils::GetContentPolicy());
1323 return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad);
1326 void FontFaceSet::DispatchFontLoadViolations(
1327 nsTArray<nsCOMPtr<nsIRunnable>>& aViolations) {
1328 if (XRE_IsContentProcess()) {
1329 nsCOMPtr<nsIEventTarget> eventTarget =
1330 mDocument->EventTargetFor(TaskCategory::Other);
1331 for (nsIRunnable* runnable : aViolations) {
1332 eventTarget->Dispatch(do_AddRef(runnable), NS_DISPATCH_NORMAL);
1334 } else {
1335 for (nsIRunnable* runnable : aViolations) {
1336 NS_DispatchToMainThread(do_AddRef(runnable));
1341 nsresult FontFaceSet::SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
1342 const gfxFontFaceSrc* aFontFaceSrc,
1343 uint8_t*& aBuffer,
1344 uint32_t& aBufferLength) {
1345 nsresult rv;
1347 gfxFontSrcPrincipal* principal = aFontToLoad->GetPrincipal();
1349 nsCOMPtr<nsIChannel> channel;
1350 // Note we are calling NS_NewChannelWithTriggeringPrincipal() with both a
1351 // node and a principal. This is because the document where the font is
1352 // being loaded might have a different origin from the principal of the
1353 // stylesheet that initiated the font load.
1354 // Further, we only get here for data: loads, so it doesn't really matter
1355 // whether we use SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT or not, to be
1356 // more restrictive we use SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT.
1357 rv = NS_NewChannelWithTriggeringPrincipal(
1358 getter_AddRefs(channel), aFontFaceSrc->mURI->get(), mDocument,
1359 principal ? principal->NodePrincipal() : nullptr,
1360 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1361 nsIContentPolicy::TYPE_FONT);
1363 NS_ENSURE_SUCCESS(rv, rv);
1365 // blocking stream is OK for data URIs
1366 nsCOMPtr<nsIInputStream> stream;
1367 rv = channel->Open(getter_AddRefs(stream));
1368 NS_ENSURE_SUCCESS(rv, rv);
1370 uint64_t bufferLength64;
1371 rv = stream->Available(&bufferLength64);
1372 NS_ENSURE_SUCCESS(rv, rv);
1373 if (bufferLength64 == 0) {
1374 return NS_ERROR_FAILURE;
1376 if (bufferLength64 > UINT32_MAX) {
1377 return NS_ERROR_FILE_TOO_BIG;
1379 aBufferLength = static_cast<uint32_t>(bufferLength64);
1381 // read all the decoded data
1382 aBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * aBufferLength));
1383 if (!aBuffer) {
1384 aBufferLength = 0;
1385 return NS_ERROR_OUT_OF_MEMORY;
1388 uint32_t numRead, totalRead = 0;
1389 while (NS_SUCCEEDED(
1390 rv = stream->Read(reinterpret_cast<char*>(aBuffer + totalRead),
1391 aBufferLength - totalRead, &numRead)) &&
1392 numRead != 0) {
1393 totalRead += numRead;
1394 if (totalRead > aBufferLength) {
1395 rv = NS_ERROR_FAILURE;
1396 break;
1400 // make sure there's a mime type
1401 if (NS_SUCCEEDED(rv)) {
1402 nsAutoCString mimeType;
1403 rv = channel->GetContentType(mimeType);
1404 aBufferLength = totalRead;
1407 if (NS_FAILED(rv)) {
1408 free(aBuffer);
1409 aBuffer = nullptr;
1410 aBufferLength = 0;
1411 return rv;
1414 return NS_OK;
1417 void FontFaceSet::OnFontFaceStatusChanged(FontFace* aFontFace) {
1418 AssertIsMainThreadOrServoFontMetricsLocked();
1420 MOZ_ASSERT(HasAvailableFontFace(aFontFace));
1422 mHasLoadingFontFacesIsDirty = true;
1424 if (aFontFace->Status() == FontFaceLoadStatus::Loading) {
1425 CheckLoadingStarted();
1426 } else {
1427 MOZ_ASSERT(aFontFace->Status() == FontFaceLoadStatus::Loaded ||
1428 aFontFace->Status() == FontFaceLoadStatus::Error);
1429 // When a font finishes downloading, nsPresContext::UserFontSetUpdated
1430 // will be called immediately afterwards to request a reflow of the
1431 // relevant elements in the document. We want to wait until the reflow
1432 // request has been done before the FontFaceSet is marked as Loaded so
1433 // that we don't briefly set the FontFaceSet to Loaded and then Loading
1434 // again once the reflow is pending. So we go around the event loop
1435 // and call CheckLoadingFinished() after the reflow has been queued.
1436 if (!mDelayedLoadCheck) {
1437 mDelayedLoadCheck = true;
1438 DispatchCheckLoadingFinishedAfterDelay();
1443 void FontFaceSet::DispatchCheckLoadingFinishedAfterDelay() {
1444 AssertIsMainThreadOrServoFontMetricsLocked();
1446 if (ServoStyleSet* set = ServoStyleSet::Current()) {
1447 // See comments in Gecko_GetFontMetrics.
1449 // We can't just dispatch the runnable below if we're not on the main
1450 // thread, since it needs to take a strong reference to the FontFaceSet,
1451 // and being a DOM object, FontFaceSet doesn't support thread-safe
1452 // refcounting.
1453 set->AppendTask(
1454 PostTraversalTask::DispatchFontFaceSetCheckLoadingFinishedAfterDelay(
1455 this));
1456 return;
1459 nsCOMPtr<nsIRunnable> checkTask =
1460 NewRunnableMethod("dom::FontFaceSet::CheckLoadingFinishedAfterDelay",
1461 this, &FontFaceSet::CheckLoadingFinishedAfterDelay);
1462 mDocument->Dispatch(TaskCategory::Other, checkTask.forget());
1465 void FontFaceSet::DidRefresh() { CheckLoadingFinished(); }
1467 void FontFaceSet::CheckLoadingFinishedAfterDelay() {
1468 mDelayedLoadCheck = false;
1469 CheckLoadingFinished();
1472 void FontFaceSet::CheckLoadingStarted() {
1473 AssertIsMainThreadOrServoFontMetricsLocked();
1475 if (!HasLoadingFontFaces()) {
1476 return;
1479 if (mStatus == FontFaceSetLoadStatus::Loading) {
1480 // We have already dispatched a loading event and replaced mReady
1481 // with a fresh, unresolved promise.
1482 return;
1485 mStatus = FontFaceSetLoadStatus::Loading;
1486 DispatchLoadingEventAndReplaceReadyPromise();
1489 void FontFaceSet::DispatchLoadingEventAndReplaceReadyPromise() {
1490 AssertIsMainThreadOrServoFontMetricsLocked();
1492 if (ServoStyleSet* set = ServoStyleSet::Current()) {
1493 // See comments in Gecko_GetFontMetrics.
1495 // We can't just dispatch the runnable below if we're not on the main
1496 // thread, since it needs to take a strong reference to the FontFaceSet,
1497 // and being a DOM object, FontFaceSet doesn't support thread-safe
1498 // refcounting. (Also, the Promise object creation must be done on
1499 // the main thread.)
1500 set->AppendTask(
1501 PostTraversalTask::DispatchLoadingEventAndReplaceReadyPromise(this));
1502 return;
1505 (new AsyncEventDispatcher(this, u"loading"_ns, CanBubble::eNo))
1506 ->PostDOMEvent();
1508 if (PrefEnabled()) {
1509 if (mReady && mReady->State() != Promise::PromiseState::Pending) {
1510 if (GetParentObject()) {
1511 ErrorResult rv;
1512 mReady = Promise::Create(GetParentObject(), rv);
1516 // We may previously have been in a state where all fonts had finished
1517 // loading and we'd set mResolveLazilyCreatedReadyPromise to make sure that
1518 // if we lazily create mReady for a consumer that we resolve it before
1519 // returning it. We're now loading fonts, so we need to clear that flag.
1520 mResolveLazilyCreatedReadyPromise = false;
1524 void FontFaceSet::UpdateHasLoadingFontFaces() {
1525 mHasLoadingFontFacesIsDirty = false;
1526 mHasLoadingFontFaces = false;
1527 for (size_t i = 0; i < mRuleFaces.Length(); i++) {
1528 FontFace* f = mRuleFaces[i].mFontFace;
1529 if (f->Status() == FontFaceLoadStatus::Loading) {
1530 mHasLoadingFontFaces = true;
1531 return;
1534 for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
1535 if (mNonRuleFaces[i].mFontFace->Status() == FontFaceLoadStatus::Loading) {
1536 mHasLoadingFontFaces = true;
1537 return;
1542 bool FontFaceSet::HasLoadingFontFaces() {
1543 if (mHasLoadingFontFacesIsDirty) {
1544 UpdateHasLoadingFontFaces();
1546 return mHasLoadingFontFaces;
1549 bool FontFaceSet::MightHavePendingFontLoads() {
1550 // Check for FontFace objects in the FontFaceSet that are still loading.
1551 if (HasLoadingFontFaces()) {
1552 return true;
1555 // Check for pending restyles or reflows, as they might cause fonts to
1556 // load as new styles apply and text runs are rebuilt.
1557 nsPresContext* presContext = GetPresContext();
1558 if (presContext && presContext->HasPendingRestyleOrReflow()) {
1559 return true;
1562 if (mDocument) {
1563 // We defer resolving mReady until the document as fully loaded.
1564 if (!mDocument->DidFireDOMContentLoaded()) {
1565 return true;
1568 // And we also wait for any CSS style sheets to finish loading, as their
1569 // styles might cause new fonts to load.
1570 if (mDocument->CSSLoader()->HasPendingLoads()) {
1571 return true;
1575 return false;
1578 void FontFaceSet::CheckLoadingFinished() {
1579 MOZ_ASSERT(NS_IsMainThread());
1581 if (mDelayedLoadCheck) {
1582 // Wait until the runnable posted in OnFontFaceStatusChanged calls us.
1583 return;
1586 if (!ReadyPromiseIsPending()) {
1587 // We've already resolved mReady (or set the flag to do that lazily) and
1588 // dispatched the loadingdone/loadingerror events.
1589 return;
1592 if (MightHavePendingFontLoads()) {
1593 // We're not finished loading yet.
1594 return;
1597 mStatus = FontFaceSetLoadStatus::Loaded;
1598 if (mReady) {
1599 mReady->MaybeResolve(this);
1600 } else {
1601 mResolveLazilyCreatedReadyPromise = true;
1604 // Now dispatch the loadingdone/loadingerror events.
1605 nsTArray<OwningNonNull<FontFace>> loaded;
1606 nsTArray<OwningNonNull<FontFace>> failed;
1608 for (size_t i = 0; i < mRuleFaces.Length(); i++) {
1609 if (!mRuleFaces[i].mLoadEventShouldFire) {
1610 continue;
1612 FontFace* f = mRuleFaces[i].mFontFace;
1613 if (f->Status() == FontFaceLoadStatus::Loaded) {
1614 loaded.AppendElement(*f);
1615 mRuleFaces[i].mLoadEventShouldFire = false;
1616 } else if (f->Status() == FontFaceLoadStatus::Error) {
1617 failed.AppendElement(*f);
1618 mRuleFaces[i].mLoadEventShouldFire = false;
1622 for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
1623 if (!mNonRuleFaces[i].mLoadEventShouldFire) {
1624 continue;
1626 FontFace* f = mNonRuleFaces[i].mFontFace;
1627 if (f->Status() == FontFaceLoadStatus::Loaded) {
1628 loaded.AppendElement(*f);
1629 mNonRuleFaces[i].mLoadEventShouldFire = false;
1630 } else if (f->Status() == FontFaceLoadStatus::Error) {
1631 failed.AppendElement(*f);
1632 mNonRuleFaces[i].mLoadEventShouldFire = false;
1636 DispatchLoadingFinishedEvent(u"loadingdone"_ns, std::move(loaded));
1638 if (!failed.IsEmpty()) {
1639 DispatchLoadingFinishedEvent(u"loadingerror"_ns, std::move(failed));
1643 void FontFaceSet::DispatchLoadingFinishedEvent(
1644 const nsAString& aType, nsTArray<OwningNonNull<FontFace>>&& aFontFaces) {
1645 FontFaceSetLoadEventInit init;
1646 init.mBubbles = false;
1647 init.mCancelable = false;
1648 init.mFontfaces = std::move(aFontFaces);
1649 RefPtr<FontFaceSetLoadEvent> event =
1650 FontFaceSetLoadEvent::Constructor(this, aType, init);
1651 (new AsyncEventDispatcher(this, event))->PostDOMEvent();
1654 // nsIDOMEventListener
1656 NS_IMETHODIMP
1657 FontFaceSet::HandleEvent(Event* aEvent) {
1658 nsString type;
1659 aEvent->GetType(type);
1661 if (!type.EqualsLiteral("DOMContentLoaded")) {
1662 return NS_ERROR_FAILURE;
1665 RemoveDOMContentLoadedListener();
1666 CheckLoadingFinished();
1668 return NS_OK;
1671 /* static */
1672 bool FontFaceSet::PrefEnabled() {
1673 return StaticPrefs::layout_css_font_loading_api_enabled();
1676 // nsICSSLoaderObserver
1678 NS_IMETHODIMP
1679 FontFaceSet::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
1680 nsresult aStatus) {
1681 CheckLoadingFinished();
1682 return NS_OK;
1685 void FontFaceSet::FlushUserFontSet() {
1686 if (mDocument) {
1687 mDocument->FlushUserFontSet();
1691 void FontFaceSet::MarkUserFontSetDirty() {
1692 if (mDocument) {
1693 // Ensure we trigger at least a style flush, that will eventually flush the
1694 // user font set. Otherwise the font loads that that flush may cause could
1695 // never be triggered.
1696 if (PresShell* presShell = mDocument->GetPresShell()) {
1697 presShell->EnsureStyleFlush();
1699 mDocument->MarkUserFontSetDirty();
1703 nsPresContext* FontFaceSet::GetPresContext() {
1704 if (!mDocument) {
1705 return nullptr;
1708 return mDocument->GetPresContext();
1711 void FontFaceSet::RefreshStandardFontLoadPrincipal() {
1712 MOZ_ASSERT(NS_IsMainThread());
1713 mStandardFontLoadPrincipal = new gfxFontSrcPrincipal(
1714 mDocument->NodePrincipal(), mDocument->PartitionedPrincipal());
1715 mAllowedFontLoads.Clear();
1716 if (mUserFontSet) {
1717 mUserFontSet->IncrementGeneration(false);
1721 void FontFaceSet::CopyNonRuleFacesTo(FontFaceSet* aFontFaceSet) const {
1722 for (const FontFaceRecord& rec : mNonRuleFaces) {
1723 ErrorResult rv;
1724 RefPtr<FontFace> f = rec.mFontFace;
1725 aFontFaceSet->Add(*f, rv);
1726 MOZ_ASSERT(!rv.Failed());
1730 // -- FontFaceSet::UserFontSet ------------------------------------------------
1732 /* virtual */
1733 bool FontFaceSet::UserFontSet::IsFontLoadAllowed(const gfxFontFaceSrc& aSrc) {
1734 return mFontFaceSet && mFontFaceSet->IsFontLoadAllowed(aSrc);
1737 /* virtual */
1738 void FontFaceSet::UserFontSet::DispatchFontLoadViolations(
1739 nsTArray<nsCOMPtr<nsIRunnable>>& aViolations) {
1740 if (mFontFaceSet) {
1741 mFontFaceSet->DispatchFontLoadViolations(aViolations);
1745 /* virtual */
1746 nsresult FontFaceSet::UserFontSet::StartLoad(
1747 gfxUserFontEntry* aUserFontEntry, const gfxFontFaceSrc* aFontFaceSrc) {
1748 if (!mFontFaceSet) {
1749 return NS_ERROR_FAILURE;
1751 return mFontFaceSet->StartLoad(aUserFontEntry, aFontFaceSrc);
1754 void FontFaceSet::UserFontSet::RecordFontLoadDone(uint32_t aFontSize,
1755 TimeStamp aDoneTime) {
1756 mDownloadCount++;
1757 mDownloadSize += aFontSize;
1758 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE, aFontSize / 1024);
1760 if (!mFontFaceSet) {
1761 return;
1764 TimeStamp navStart = mFontFaceSet->GetNavigationStartTimeStamp();
1765 TimeStamp zero;
1766 if (navStart != zero) {
1767 Telemetry::AccumulateTimeDelta(Telemetry::WEBFONT_DOWNLOAD_TIME_AFTER_START,
1768 navStart, aDoneTime);
1772 /* virtual */
1773 nsresult FontFaceSet::UserFontSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
1774 const char* aMessage,
1775 uint32_t aFlags,
1776 nsresult aStatus) {
1777 if (!mFontFaceSet) {
1778 return NS_ERROR_FAILURE;
1780 return mFontFaceSet->LogMessage(aUserFontEntry, aMessage, aFlags, aStatus);
1783 /* virtual */
1784 nsresult FontFaceSet::UserFontSet::SyncLoadFontData(
1785 gfxUserFontEntry* aFontToLoad, const gfxFontFaceSrc* aFontFaceSrc,
1786 uint8_t*& aBuffer, uint32_t& aBufferLength) {
1787 if (!mFontFaceSet) {
1788 return NS_ERROR_FAILURE;
1790 return mFontFaceSet->SyncLoadFontData(aFontToLoad, aFontFaceSrc, aBuffer,
1791 aBufferLength);
1794 /* virtual */
1795 bool FontFaceSet::UserFontSet::GetPrivateBrowsing() {
1796 return mFontFaceSet && mFontFaceSet->mPrivateBrowsing;
1799 /* virtual */
1800 void FontFaceSet::UserFontSet::DoRebuildUserFontSet() {
1801 if (!mFontFaceSet) {
1802 return;
1804 mFontFaceSet->MarkUserFontSetDirty();
1807 /* virtual */
1808 already_AddRefed<gfxUserFontEntry>
1809 FontFaceSet::UserFontSet::CreateUserFontEntry(
1810 const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight,
1811 StretchRange aStretch, SlantStyleRange aStyle,
1812 const nsTArray<gfxFontFeature>& aFeatureSettings,
1813 const nsTArray<gfxFontVariation>& aVariationSettings,
1814 uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges,
1815 StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) {
1816 RefPtr<gfxUserFontEntry> entry = new FontFace::Entry(
1817 this, aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings,
1818 aVariationSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay,
1819 aRangeFlags);
1820 return entry.forget();
1823 #undef LOG_ENABLED
1824 #undef LOG