/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FontFaceSet.h" #include "gfxFontConstants.h" #include "gfxFontSrcPrincipal.h" #include "gfxFontSrcURI.h" #include "gfxFontUtils.h" #include "FontPreloader.h" #include "mozilla/css/Loader.h" #include "mozilla/dom/CSSFontFaceRule.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/FontFaceImpl.h" #include "mozilla/dom/FontFaceSetBinding.h" #include "mozilla/dom/FontFaceSetDocumentImpl.h" #include "mozilla/dom/FontFaceSetWorkerImpl.h" #include "mozilla/dom/FontFaceSetIterator.h" #include "mozilla/dom/FontFaceSetLoadEvent.h" #include "mozilla/dom/FontFaceSetLoadEventBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/FontPropertyTypes.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Logging.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoCSSParser.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/ServoUtils.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/Telemetry.h" #include "mozilla/LoadInfo.h" #include "nsComponentManagerUtils.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsDeviceContext.h" #include "nsFontFaceLoader.h" #include "nsIConsoleService.h" #include "nsIContentPolicy.h" #include "nsIDocShell.h" #include "mozilla/dom/Document.h" #include "nsILoadContext.h" #include "nsINetworkPredictor.h" #include "nsIPrincipal.h" #include "nsIWebNavigation.h" #include "nsNetUtil.h" #include "nsIInputStream.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" #include "nsPrintfCString.h" #include "nsUTF8Utils.h" #include "nsDOMNavigationTiming.h" #include "ReferrerInfo.h" using namespace mozilla; using namespace mozilla::css; using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_CLASS(FontFaceSet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mImpl->GetDocument()); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady); for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleFaces[i].mFontFace); } for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonRuleFaces[i].mFontFace); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelper) tmp->Destroy(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady); for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_UNLINK(mRuleFaces[i].mFontFace); } for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonRuleFaces[i].mFontFace); } NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_ADDREF_INHERITED(FontFaceSet, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(FontFaceSet, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FontFaceSet) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) FontFaceSet::FontFaceSet(nsIGlobalObject* aParent) : DOMEventTargetHelper(aParent) {} FontFaceSet::~FontFaceSet() { // Assert that we don't drop any FontFaceSet objects during a Servo traversal, // since PostTraversalTask objects can hold raw pointers to FontFaceSets. MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal()); Destroy(); } /* static */ bool FontFaceSet::IsEnabled() { if (NS_IsMainThread()) { return StaticPrefs::layout_css_font_loading_api_enabled(); } return StaticPrefs::layout_css_font_loading_api_workers_enabled(); } /* static */ already_AddRefed FontFaceSet::CreateForDocument( dom::Document* aDocument) { RefPtr set = new FontFaceSet(aDocument->GetScopeObject()); RefPtr impl = new FontFaceSetDocumentImpl(set, aDocument); impl->Initialize(); set->mImpl = std::move(impl); return set.forget(); } /* static */ already_AddRefed FontFaceSet::CreateForWorker( nsIGlobalObject* aParent, WorkerPrivate* aWorkerPrivate) { RefPtr set = new FontFaceSet(aParent); RefPtr impl = new FontFaceSetWorkerImpl(set); if (NS_WARN_IF(!impl->Initialize(aWorkerPrivate))) { return nullptr; } set->mImpl = std::move(impl); return set.forget(); } JSObject* FontFaceSet::WrapObject(JSContext* aContext, JS::Handle aGivenProto) { return FontFaceSet_Binding::Wrap(aContext, this, aGivenProto); } void FontFaceSet::Destroy() { mImpl->Destroy(); } already_AddRefed FontFaceSet::Load(JSContext* aCx, const nsACString& aFont, const nsAString& aText, ErrorResult& aRv) { FlushUserFontSet(); nsTArray> promises; nsTArray faces; mImpl->FindMatchingFontFaces(aFont, aText, faces, aRv); if (aRv.Failed()) { return nullptr; } for (FontFace* f : faces) { RefPtr promise = f->Load(aRv); if (aRv.Failed()) { return nullptr; } if (!promises.AppendElement(promise, fallible)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } } return Promise::All(aCx, promises, aRv); } bool FontFaceSet::Check(const nsACString& aFont, const nsAString& aText, ErrorResult& aRv) { FlushUserFontSet(); nsTArray faces; mImpl->FindMatchingFontFaces(aFont, aText, faces, aRv); if (aRv.Failed()) { return false; } for (FontFace* f : faces) { if (f->Status() != FontFaceLoadStatus::Loaded) { return false; } } return true; } bool FontFaceSet::ReadyPromiseIsPending() const { return mReady ? mReady->State() == Promise::PromiseState::Pending : !mResolveLazilyCreatedReadyPromise; } Promise* FontFaceSet::GetReady(ErrorResult& aRv) { mImpl->EnsureReady(); if (!mReady) { nsCOMPtr global = GetParentObject(); mReady = Promise::Create(global, aRv); if (!mReady) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } if (mResolveLazilyCreatedReadyPromise) { mReady->MaybeResolve(this); mResolveLazilyCreatedReadyPromise = false; } } return mReady; } FontFaceSetLoadStatus FontFaceSet::Status() { return mImpl->Status(); } #ifdef DEBUG bool FontFaceSet::HasRuleFontFace(FontFace* aFontFace) { for (size_t i = 0; i < mRuleFaces.Length(); i++) { if (mRuleFaces[i].mFontFace == aFontFace) { return true; } } return false; } #endif void FontFaceSet::Add(FontFace& aFontFace, ErrorResult& aRv) { FlushUserFontSet(); FontFaceImpl* fontImpl = aFontFace.GetImpl(); MOZ_ASSERT(fontImpl); if (!mImpl->Add(fontImpl, aRv)) { return; } MOZ_ASSERT(!aRv.Failed()); #ifdef DEBUG for (const FontFaceRecord& rec : mNonRuleFaces) { MOZ_ASSERT(rec.mFontFace != &aFontFace, "FontFace should not occur in mNonRuleFaces twice"); } #endif FontFaceRecord* rec = mNonRuleFaces.AppendElement(); rec->mFontFace = &aFontFace; rec->mOrigin = Nothing(); rec->mLoadEventShouldFire = fontImpl->Status() == FontFaceLoadStatus::Unloaded || fontImpl->Status() == FontFaceLoadStatus::Loading; } void FontFaceSet::Clear() { nsTArray oldRecords = std::move(mNonRuleFaces); mImpl->Clear(); } bool FontFaceSet::Delete(FontFace& aFontFace) { // Hold onto a strong reference to make sure that when we remove FontFace from // the list, the FontFaceImpl does not get freed right away. We need to check // the FontFaceSetImpl first. RefPtr fontImpl = aFontFace.GetImpl(); MOZ_ASSERT(fontImpl); // Ensure that we remove from mNonRuleFaces first. This is important so that // when we check to see if all of the fonts have finished loading, the list in // FontFaceSet and FontFaceSetImpl match. bool removed = false; for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { if (mNonRuleFaces[i].mFontFace == &aFontFace) { mNonRuleFaces.RemoveElementAt(i); removed = true; break; } } if (!mImpl->Delete(fontImpl)) { MOZ_ASSERT(!removed, "Missing rule present in Impl!"); } else { MOZ_ASSERT(removed, "Rule present but missing in Impl!"); } return removed; } bool FontFaceSet::HasAvailableFontFace(FontFace* aFontFace) { return aFontFace->GetImpl()->IsInFontFaceSet(mImpl); } bool FontFaceSet::Has(FontFace& aFontFace) { FlushUserFontSet(); return HasAvailableFontFace(&aFontFace); } FontFace* FontFaceSet::GetFontFaceAt(uint32_t aIndex) { FlushUserFontSet(); if (aIndex < mRuleFaces.Length()) { auto& entry = mRuleFaces[aIndex]; if (entry.mOrigin.value() != StyleOrigin::Author) { return nullptr; } return entry.mFontFace; } aIndex -= mRuleFaces.Length(); if (aIndex < mNonRuleFaces.Length()) { return mNonRuleFaces[aIndex].mFontFace; } return nullptr; } uint32_t FontFaceSet::Size() { FlushUserFontSet(); // Web IDL objects can only expose array index properties up to INT32_MAX. size_t total = mNonRuleFaces.Length(); for (const auto& entry : mRuleFaces) { if (entry.mOrigin.value() == StyleOrigin::Author) { ++total; } } return std::min(total, INT32_MAX); } uint32_t FontFaceSet::SizeIncludingNonAuthorOrigins() { FlushUserFontSet(); // Web IDL objects can only expose array index properties up to INT32_MAX. size_t total = mRuleFaces.Length() + mNonRuleFaces.Length(); return std::min(total, INT32_MAX); } already_AddRefed FontFaceSet::Entries() { RefPtr it = new FontFaceSetIterator(this, true); return it.forget(); } already_AddRefed FontFaceSet::Values() { RefPtr it = new FontFaceSetIterator(this, false); return it.forget(); } void FontFaceSet::ForEach(JSContext* aCx, FontFaceSetForEachCallback& aCallback, JS::Handle aThisArg, ErrorResult& aRv) { JS::Rooted thisArg(aCx, aThisArg); for (size_t i = 0; i < SizeIncludingNonAuthorOrigins(); i++) { RefPtr face = GetFontFaceAt(i); if (!face) { // The font at index |i| is a non-Author origin font, which we shouldn't // expose per spec. continue; } aCallback.Call(thisArg, *face, *face, *this, aRv); if (aRv.Failed()) { return; } } } bool FontFaceSet::UpdateRules(const nsTArray& aRules) { // The impl object handles the callbacks for recreating the mRulesFaces array. nsTArray oldRecords = std::move(mRuleFaces); return mImpl->UpdateRules(aRules); } void FontFaceSet::InsertRuleFontFace(FontFace* aFontFace, StyleOrigin aOrigin) { MOZ_ASSERT(!HasRuleFontFace(aFontFace)); FontFaceRecord* rec = mRuleFaces.AppendElement(); rec->mFontFace = aFontFace; rec->mOrigin = Some(aOrigin); rec->mLoadEventShouldFire = aFontFace->Status() == FontFaceLoadStatus::Unloaded || aFontFace->Status() == FontFaceLoadStatus::Loading; } void FontFaceSet::DidRefresh() { mImpl->CheckLoadingFinished(); } void FontFaceSet::DispatchLoadingEventAndReplaceReadyPromise() { gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked(); if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { // See comments in Gecko_GetFontMetrics. // // We can't just dispatch the runnable below if we're not on the main // thread, since it needs to take a strong reference to the FontFaceSet, // and being a DOM object, FontFaceSet doesn't support thread-safe // refcounting. (Also, the Promise object creation must be done on // the main thread.) set->AppendTask( PostTraversalTask::DispatchLoadingEventAndReplaceReadyPromise(this)); return; } (new AsyncEventDispatcher(this, u"loading"_ns, CanBubble::eNo)) ->PostDOMEvent(); if (IsEnabled()) { if (mReady && mReady->State() != Promise::PromiseState::Pending) { if (GetParentObject()) { ErrorResult rv; mReady = Promise::Create(GetParentObject(), rv); } } // We may previously have been in a state where all fonts had finished // loading and we'd set mResolveLazilyCreatedReadyPromise to make sure that // if we lazily create mReady for a consumer that we resolve it before // returning it. We're now loading fonts, so we need to clear that flag. mResolveLazilyCreatedReadyPromise = false; } } void FontFaceSet::MaybeResolve() { if (mReady) { mReady->MaybeResolve(this); } else { mResolveLazilyCreatedReadyPromise = true; } // Now dispatch the loadingdone/loadingerror events. nsTArray> loaded; nsTArray> failed; auto checkStatus = [&](nsTArray& faces) -> void { for (auto& face : faces) { if (!face.mLoadEventShouldFire) { continue; } FontFace* f = face.mFontFace; switch (f->Status()) { case FontFaceLoadStatus::Unloaded: break; case FontFaceLoadStatus::Loaded: loaded.AppendElement(*f); face.mLoadEventShouldFire = false; break; case FontFaceLoadStatus::Error: failed.AppendElement(*f); face.mLoadEventShouldFire = false; break; case FontFaceLoadStatus::Loading: // We should've returned above at MightHavePendingFontLoads()! case FontFaceLoadStatus::EndGuard_: MOZ_ASSERT_UNREACHABLE("unexpected FontFaceLoadStatus"); break; } } }; checkStatus(mRuleFaces); checkStatus(mNonRuleFaces); DispatchLoadingFinishedEvent(u"loadingdone"_ns, std::move(loaded)); if (!failed.IsEmpty()) { DispatchLoadingFinishedEvent(u"loadingerror"_ns, std::move(failed)); } } void FontFaceSet::DispatchLoadingFinishedEvent( const nsAString& aType, nsTArray>&& aFontFaces) { FontFaceSetLoadEventInit init; init.mBubbles = false; init.mCancelable = false; init.mFontfaces = std::move(aFontFaces); RefPtr event = FontFaceSetLoadEvent::Constructor(this, aType, init); (new AsyncEventDispatcher(this, event))->PostDOMEvent(); } void FontFaceSet::FlushUserFontSet() { mImpl->FlushUserFontSet(); } void FontFaceSet::RefreshStandardFontLoadPrincipal() { MOZ_ASSERT(NS_IsMainThread()); mImpl->RefreshStandardFontLoadPrincipal(); } void FontFaceSet::CopyNonRuleFacesTo(FontFaceSet* aFontFaceSet) const { for (const FontFaceRecord& rec : mNonRuleFaces) { IgnoredErrorResult rv; RefPtr f = rec.mFontFace; aFontFaceSet->Add(*f, rv); MOZ_ASSERT(!rv.Failed()); } } #undef LOG_ENABLED #undef LOG