diff options
Diffstat (limited to 'layout/style/FontFaceSetImpl.cpp')
-rw-r--r-- | layout/style/FontFaceSetImpl.cpp | 924 |
1 files changed, 924 insertions, 0 deletions
diff --git a/layout/style/FontFaceSetImpl.cpp b/layout/style/FontFaceSetImpl.cpp new file mode 100644 index 0000000000..13bdd73060 --- /dev/null +++ b/layout/style/FontFaceSetImpl.cpp @@ -0,0 +1,924 @@ +/* -*- 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 "FontFaceSetImpl.h" + +#include "gfxFontConstants.h" +#include "gfxFontSrcPrincipal.h" +#include "gfxFontSrcURI.h" +#include "gfxFontUtils.h" +#include "gfxPlatformFontList.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/FontFaceSet.h" +#include "mozilla/dom/FontFaceSetBinding.h" +#include "mozilla/dom/FontFaceSetLoadEvent.h" +#include "mozilla/dom/FontFaceSetLoadEventBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerRunnable.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 "nsContentUtils.h" +#include "nsDeviceContext.h" +#include "nsFontFaceLoader.h" +#include "nsIConsoleService.h" +#include "nsIContentPolicy.h" +#include "nsIDocShell.h" +#include "nsILoadContext.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; + +#define LOG(args) \ + MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug) + +NS_IMPL_ISUPPORTS0(FontFaceSetImpl) + +FontFaceSetImpl::FontFaceSetImpl(FontFaceSet* aOwner) + : mMutex("mozilla::dom::FontFaceSetImpl"), + mOwner(aOwner), + mStatus(FontFaceSetLoadStatus::Loaded), + mNonRuleFacesDirty(false), + mHasLoadingFontFaces(false), + mHasLoadingFontFacesIsDirty(false), + mDelayedLoadCheck(false), + mBypassCache(false), + mPrivateBrowsing(false) {} + +FontFaceSetImpl::~FontFaceSetImpl() { + // 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(); +} + +void FontFaceSetImpl::Destroy() { + nsTArray<FontFaceRecord> nonRuleFaces; + nsRefPtrHashtable<nsCStringHashKey, gfxUserFontFamily> fontFamilies; + + { + RecursiveMutexAutoLock lock(mMutex); + for (const auto& key : mLoaders.Keys()) { + key->Cancel(); + } + + mLoaders.Clear(); + nonRuleFaces = std::move(mNonRuleFaces); + fontFamilies = std::move(mFontFamilies); + mOwner = nullptr; + } + + gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); + if (fp) { + fp->RemoveUserFontSet(this); + } +} + +void FontFaceSetImpl::ParseFontShorthandForMatching( + const nsACString& aFont, StyleFontFamilyList& aFamilyList, + FontWeight& aWeight, FontStretch& aStretch, FontSlantStyle& aStyle, + ErrorResult& aRv) { + RefPtr<URLExtraData> url = GetURLExtraData(); + if (!url) { + aRv.ThrowInvalidStateError("Missing URLExtraData"); + return; + } + + if (!ServoCSSParser::ParseFontShorthandForMatching( + aFont, url, aFamilyList, aStyle, aStretch, aWeight)) { + aRv.ThrowSyntaxError("Invalid font shorthand"); + return; + } +} + +static bool HasAnyCharacterInUnicodeRange(gfxUserFontEntry* aEntry, + const nsAString& aInput) { + const char16_t* p = aInput.Data(); + const char16_t* end = p + aInput.Length(); + + while (p < end) { + uint32_t c = UTF16CharEnumerator::NextChar(&p, end); + if (aEntry->CharacterInUnicodeRange(c)) { + return true; + } + } + return false; +} + +void FontFaceSetImpl::FindMatchingFontFaces(const nsACString& aFont, + const nsAString& aText, + nsTArray<FontFace*>& aFontFaces, + ErrorResult& aRv) { + RecursiveMutexAutoLock lock(mMutex); + + StyleFontFamilyList familyList; + FontWeight weight; + FontStretch stretch; + FontSlantStyle italicStyle; + ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle, + aRv); + if (aRv.Failed()) { + return; + } + + gfxFontStyle style; + style.style = italicStyle; + style.weight = weight; + style.stretch = stretch; + + // Set of FontFaces that we want to return. + nsTHashSet<FontFace*> matchingFaces; + + for (const StyleSingleFontFamily& fontFamilyName : familyList.list.AsSpan()) { + if (!fontFamilyName.IsFamilyName()) { + continue; + } + + const auto& name = fontFamilyName.AsFamilyName(); + RefPtr<gfxFontFamily> family = + LookupFamily(nsAtomCString(name.name.AsAtom())); + + if (!family) { + continue; + } + + AutoTArray<gfxFontEntry*, 4> entries; + family->FindAllFontsForStyle(style, entries); + + for (gfxFontEntry* e : entries) { + FontFaceImpl::Entry* entry = static_cast<FontFaceImpl::Entry*>(e); + if (HasAnyCharacterInUnicodeRange(entry, aText)) { + entry->FindFontFaceOwners(matchingFaces); + } + } + } + + if (matchingFaces.IsEmpty()) { + return; + } + + // Add all FontFaces in matchingFaces to aFontFaces, in the order + // they appear in the FontFaceSet. + FindMatchingFontFaces(matchingFaces, aFontFaces); +} + +void FontFaceSetImpl::FindMatchingFontFaces( + const nsTHashSet<FontFace*>& aMatchingFaces, + nsTArray<FontFace*>& aFontFaces) { + RecursiveMutexAutoLock lock(mMutex); + for (FontFaceRecord& record : mNonRuleFaces) { + FontFace* owner = record.mFontFace->GetOwner(); + if (owner && aMatchingFaces.Contains(owner)) { + aFontFaces.AppendElement(owner); + } + } +} + +bool FontFaceSetImpl::ReadyPromiseIsPending() const { + RecursiveMutexAutoLock lock(mMutex); + return mOwner && mOwner->ReadyPromiseIsPending(); +} + +FontFaceSetLoadStatus FontFaceSetImpl::Status() { + RecursiveMutexAutoLock lock(mMutex); + FlushUserFontSet(); + return mStatus; +} + +bool FontFaceSetImpl::Add(FontFaceImpl* aFontFace, ErrorResult& aRv) { + RecursiveMutexAutoLock lock(mMutex); + FlushUserFontSet(); + + if (aFontFace->IsInFontFaceSet(this)) { + return false; + } + + if (aFontFace->HasRule()) { + aRv.ThrowInvalidModificationError( + "Can't add face to FontFaceSet that comes from an @font-face rule"); + return false; + } + + aFontFace->AddFontFaceSet(this); + +#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(); + + mNonRuleFacesDirty = true; + MarkUserFontSetDirty(); + mHasLoadingFontFacesIsDirty = true; + CheckLoadingStarted(); + return true; +} + +void FontFaceSetImpl::Clear() { + RecursiveMutexAutoLock lock(mMutex); + FlushUserFontSet(); + + if (mNonRuleFaces.IsEmpty()) { + return; + } + + for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { + FontFaceImpl* f = mNonRuleFaces[i].mFontFace; + f->RemoveFontFaceSet(this); + } + + mNonRuleFaces.Clear(); + mNonRuleFacesDirty = true; + MarkUserFontSetDirty(); + mHasLoadingFontFacesIsDirty = true; + CheckLoadingFinished(); +} + +bool FontFaceSetImpl::Delete(FontFaceImpl* aFontFace) { + RecursiveMutexAutoLock lock(mMutex); + FlushUserFontSet(); + + if (aFontFace->HasRule()) { + return false; + } + + bool removed = false; + for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { + if (mNonRuleFaces[i].mFontFace == aFontFace) { + mNonRuleFaces.RemoveElementAt(i); + removed = true; + break; + } + } + if (!removed) { + return false; + } + + aFontFace->RemoveFontFaceSet(this); + + mNonRuleFacesDirty = true; + MarkUserFontSetDirty(); + mHasLoadingFontFacesIsDirty = true; + CheckLoadingFinished(); + return true; +} + +bool FontFaceSetImpl::HasAvailableFontFace(FontFaceImpl* aFontFace) { + return aFontFace->IsInFontFaceSet(this); +} + +void FontFaceSetImpl::RemoveLoader(nsFontFaceLoader* aLoader) { + RecursiveMutexAutoLock lock(mMutex); + mLoaders.RemoveEntry(aLoader); +} + +void FontFaceSetImpl::InsertNonRuleFontFace(FontFaceImpl* aFontFace, + bool& aFontSetModified) { + gfxUserFontAttributes attr; + if (!aFontFace->GetAttributes(attr)) { + // If there is no family name, this rule cannot contribute a + // usable font, so there is no point in processing it further. + return; + } + + nsAutoCString family(attr.mFamilyName); + + // Just create a new font entry if we haven't got one already. + if (!aFontFace->GetUserFontEntry()) { + // XXX Should we be checking mLocalRulesUsed like InsertRuleFontFace does? + RefPtr<gfxUserFontEntry> entry = FindOrCreateUserFontEntryFromFontFace( + aFontFace, std::move(attr), StyleOrigin::Author); + if (!entry) { + return; + } + aFontFace->SetUserFontEntry(entry); + } + + aFontSetModified = true; + AddUserFontEntry(family, aFontFace->GetUserFontEntry()); +} + +void FontFaceSetImpl::UpdateUserFontEntry(gfxUserFontEntry* aEntry, + gfxUserFontAttributes&& aAttr) { + MOZ_ASSERT(NS_IsMainThread()); + + bool resetFamilyName = !aEntry->mFamilyName.IsEmpty() && + aEntry->mFamilyName != aAttr.mFamilyName; + // aFontFace already has a user font entry, so we update its attributes + // rather than creating a new one. + aEntry->UpdateAttributes(std::move(aAttr)); + // If the family name has changed, remove the entry from its current family + // and clear the mFamilyName field so it can be reset when added to a new + // family. + if (resetFamilyName) { + RefPtr<gfxUserFontFamily> family = LookupFamily(aEntry->mFamilyName); + if (family) { + family->RemoveFontEntry(aEntry); + } + aEntry->mFamilyName.Truncate(0); + } +} + +class FontFaceSetImpl::UpdateUserFontEntryRunnable final + : public WorkerMainThreadRunnable { + public: + UpdateUserFontEntryRunnable(FontFaceSetImpl* aSet, gfxUserFontEntry* aEntry, + gfxUserFontAttributes& aAttr) + : WorkerMainThreadRunnable( + GetCurrentThreadWorkerPrivate(), + "FontFaceSetImpl :: FindOrCreateUserFontEntryFromFontFace"_ns), + mSet(aSet), + mEntry(aEntry), + mAttr(aAttr) {} + + bool MainThreadRun() override { + mSet->UpdateUserFontEntry(mEntry, std::move(mAttr)); + return true; + } + + private: + FontFaceSetImpl* mSet; + gfxUserFontEntry* mEntry; + gfxUserFontAttributes& mAttr; +}; + +// TODO(emilio): Should this take an nsAtom* aFamilyName instead? +// +// All callers have one handy. +/* static */ +already_AddRefed<gfxUserFontEntry> +FontFaceSetImpl::FindOrCreateUserFontEntryFromFontFace( + FontFaceImpl* aFontFace, gfxUserFontAttributes&& aAttr, + StyleOrigin aOrigin) { + FontFaceSetImpl* set = aFontFace->GetPrimaryFontFaceSet(); + + RefPtr<gfxUserFontEntry> existingEntry = aFontFace->GetUserFontEntry(); + if (existingEntry) { + if (NS_IsMainThread()) { + set->UpdateUserFontEntry(existingEntry, std::move(aAttr)); + } else { + auto task = + MakeRefPtr<UpdateUserFontEntryRunnable>(set, existingEntry, aAttr); + IgnoredErrorResult ignoredRv; + task->Dispatch(Canceling, ignoredRv); + } + return existingEntry.forget(); + } + + // set up src array + nsTArray<gfxFontFaceSrc> srcArray; + + if (aFontFace->HasFontData()) { + gfxFontFaceSrc* face = srcArray.AppendElement(); + if (!face) { + return nullptr; + } + + face->mSourceType = gfxFontFaceSrc::eSourceType_Buffer; + face->mBuffer = aFontFace->TakeBufferSource(); + } else { + size_t len = aAttr.mSources.Length(); + for (size_t i = 0; i < len; ++i) { + gfxFontFaceSrc* face = srcArray.AppendElement(); + const auto& component = aAttr.mSources[i]; + switch (component.tag) { + case StyleFontFaceSourceListComponent::Tag::Local: { + nsAtom* atom = component.AsLocal(); + face->mLocalName.Append(nsAtomCString(atom)); + face->mSourceType = gfxFontFaceSrc::eSourceType_Local; + face->mURI = nullptr; + face->mFormatHint = StyleFontFaceSourceFormatKeyword::None; + break; + } + + case StyleFontFaceSourceListComponent::Tag::Url: { + face->mSourceType = gfxFontFaceSrc::eSourceType_URL; + const StyleCssUrl* url = component.AsUrl(); + nsIURI* uri = url->GetURI(); + face->mURI = uri ? new gfxFontSrcURI(uri) : nullptr; + const URLExtraData& extraData = url->ExtraData(); + face->mReferrerInfo = extraData.ReferrerInfo(); + + // agent and user stylesheets are treated slightly differently, + // the same-site origin check and access control headers are + // enforced against the sheet principal rather than the document + // principal to allow user stylesheets to include @font-face rules + if (aOrigin == StyleOrigin::User || + aOrigin == StyleOrigin::UserAgent) { + face->mUseOriginPrincipal = true; + face->mOriginPrincipal = new gfxFontSrcPrincipal( + extraData.Principal(), extraData.Principal()); + } + + face->mLocalName.Truncate(); + face->mFormatHint = StyleFontFaceSourceFormatKeyword::None; + face->mTechFlags = StyleFontFaceSourceTechFlags::Empty(); + + if (i + 1 < len) { + // Check for a format hint. + const auto& next = aAttr.mSources[i + 1]; + switch (next.tag) { + case StyleFontFaceSourceListComponent::Tag::FormatHintKeyword: + face->mFormatHint = next.format_hint_keyword._0; + i++; + break; + case StyleFontFaceSourceListComponent::Tag::FormatHintString: { + nsDependentCSubstring valueString( + reinterpret_cast<const char*>( + next.format_hint_string.utf8_bytes), + next.format_hint_string.length); + + if (valueString.LowerCaseEqualsASCII("woff")) { + face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff; + } else if (valueString.LowerCaseEqualsASCII("woff2")) { + face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff2; + } else if (valueString.LowerCaseEqualsASCII("opentype")) { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::Opentype; + } else if (valueString.LowerCaseEqualsASCII("truetype")) { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::Truetype; + } else if (valueString.LowerCaseEqualsASCII("truetype-aat")) { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::Truetype; + } else if (valueString.LowerCaseEqualsASCII( + "embedded-opentype")) { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::EmbeddedOpentype; + } else if (valueString.LowerCaseEqualsASCII("svg")) { + face->mFormatHint = StyleFontFaceSourceFormatKeyword::Svg; + } else if (StaticPrefs::layout_css_font_variations_enabled()) { + // Non-standard values that Firefox accepted, for back-compat; + // these are superseded by the tech() function. + if (valueString.LowerCaseEqualsASCII("woff-variations")) { + face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff; + } else if (valueString.LowerCaseEqualsASCII( + "woff2-variations")) { + face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff2; + } else if (valueString.LowerCaseEqualsASCII( + "opentype-variations")) { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::Opentype; + } else if (valueString.LowerCaseEqualsASCII( + "truetype-variations")) { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::Truetype; + } else { + face->mFormatHint = + StyleFontFaceSourceFormatKeyword::Unknown; + } + } else { + // unknown format specified, mark to distinguish from the + // case where no format hints are specified + face->mFormatHint = StyleFontFaceSourceFormatKeyword::Unknown; + } + i++; + break; + } + case StyleFontFaceSourceListComponent::Tag::TechFlags: + case StyleFontFaceSourceListComponent::Tag::Local: + case StyleFontFaceSourceListComponent::Tag::Url: + break; + } + } + + if (i + 1 < len) { + // Check for a set of font-technologies flags. + const auto& next = aAttr.mSources[i + 1]; + if (next.IsTechFlags()) { + face->mTechFlags = next.AsTechFlags(); + i++; + } + } + + if (!face->mURI) { + // if URI not valid, omit from src array + srcArray.RemoveLastElement(); + NS_WARNING("null url in @font-face rule"); + continue; + } + break; + } + + case StyleFontFaceSourceListComponent::Tag::FormatHintKeyword: + case StyleFontFaceSourceListComponent::Tag::FormatHintString: + case StyleFontFaceSourceListComponent::Tag::TechFlags: + MOZ_ASSERT_UNREACHABLE( + "Should always come after a URL source, and be consumed already"); + break; + } + } + } + + if (srcArray.IsEmpty()) { + return nullptr; + } + + return set->FindOrCreateUserFontEntry(std::move(srcArray), std::move(aAttr)); +} + +nsresult FontFaceSetImpl::LogMessage(gfxUserFontEntry* aUserFontEntry, + uint32_t aSrcIndex, const char* aMessage, + uint32_t aFlags, nsresult aStatus) { + nsAutoCString familyName; + nsAutoCString fontURI; + aUserFontEntry->GetFamilyNameAndURIForLogging(aSrcIndex, familyName, fontURI); + + nsAutoCString weightString; + aUserFontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + aUserFontEntry->Stretch().ToString(stretchString); + nsPrintfCString message( + "downloadable font: %s " + "(font-family: \"%s\" style:%s weight:%s stretch:%s src index:%d)", + aMessage, familyName.get(), + aUserFontEntry->IsItalic() ? "italic" : "normal", // XXX todo: oblique? + weightString.get(), stretchString.get(), aSrcIndex); + + if (NS_FAILED(aStatus)) { + message.AppendLiteral(": "); + switch (aStatus) { + case NS_ERROR_DOM_BAD_URI: + message.AppendLiteral("bad URI or cross-site access not allowed"); + break; + case NS_ERROR_CONTENT_BLOCKED: + message.AppendLiteral("content blocked"); + break; + default: + message.AppendLiteral("status="); + message.AppendInt(static_cast<uint32_t>(aStatus)); + break; + } + } + message.AppendLiteral(" source: "); + message.Append(fontURI); + + LOG(("userfonts (%p) %s", this, message.get())); + + if (GetCurrentThreadWorkerPrivate()) { + // TODO(aosmond): Log to the console for workers. See bug 1778537. + return NS_OK; + } + + nsCOMPtr<nsIConsoleService> console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (!console) { + return NS_ERROR_NOT_AVAILABLE; + } + + // try to give the user an indication of where the rule came from + RawServoFontFaceRule* rule = FindRuleForUserFontEntry(aUserFontEntry); + nsString href; + nsAutoCString text; + uint32_t line = 0; + uint32_t column = 0; + if (rule) { + Servo_FontFaceRule_GetCssText(rule, &text); + Servo_FontFaceRule_GetSourceLocation(rule, &line, &column); + // FIXME We need to figure out an approach to get the style sheet + // of this raw rule. See bug 1450903. +#if 0 + StyleSheet* sheet = rule->GetStyleSheet(); + // if the style sheet is removed while the font is loading can be null + if (sheet) { + nsCString spec = sheet->GetSheetURI()->GetSpecOrDefault(); + CopyUTF8toUTF16(spec, href); + } else { + NS_WARNING("null parent stylesheet for @font-face rule"); + href.AssignLiteral("unknown"); + } +#endif + // Leave href empty if we don't know how to get the correct sheet. + } + + nsresult rv; + nsCOMPtr<nsIScriptError> scriptError = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(message), + href, // file + NS_ConvertUTF8toUTF16(text), // src line + line, column, + aFlags, // flags + "CSS Loader", // category (make separate?) + GetInnerWindowID()); + if (NS_SUCCEEDED(rv)) { + console->LogMessage(scriptError); + } + + return NS_OK; +} + +nsresult FontFaceSetImpl::SyncLoadFontData(gfxUserFontEntry* aFontToLoad, + const gfxFontFaceSrc* aFontFaceSrc, + uint8_t*& aBuffer, + uint32_t& aBufferLength) { + nsCOMPtr<nsIChannel> channel; + nsresult rv = CreateChannelForSyncLoadFontData(getter_AddRefs(channel), + aFontToLoad, aFontFaceSrc); + NS_ENSURE_SUCCESS(rv, rv); + + // blocking stream is OK for data URIs + nsCOMPtr<nsIInputStream> stream; + rv = channel->Open(getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t bufferLength64; + rv = stream->Available(&bufferLength64); + NS_ENSURE_SUCCESS(rv, rv); + if (bufferLength64 == 0) { + return NS_ERROR_FAILURE; + } + if (bufferLength64 > UINT32_MAX) { + return NS_ERROR_FILE_TOO_BIG; + } + aBufferLength = static_cast<uint32_t>(bufferLength64); + + // read all the decoded data + aBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * aBufferLength)); + if (!aBuffer) { + aBufferLength = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t numRead, totalRead = 0; + while (NS_SUCCEEDED( + rv = stream->Read(reinterpret_cast<char*>(aBuffer + totalRead), + aBufferLength - totalRead, &numRead)) && + numRead != 0) { + totalRead += numRead; + if (totalRead > aBufferLength) { + rv = NS_ERROR_FAILURE; + break; + } + } + + // make sure there's a mime type + if (NS_SUCCEEDED(rv)) { + nsAutoCString mimeType; + rv = channel->GetContentType(mimeType); + aBufferLength = totalRead; + } + + if (NS_FAILED(rv)) { + free(aBuffer); + aBuffer = nullptr; + aBufferLength = 0; + return rv; + } + + return NS_OK; +} + +void FontFaceSetImpl::OnFontFaceStatusChanged(FontFaceImpl* aFontFace) { + gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked(); + RecursiveMutexAutoLock lock(mMutex); + MOZ_ASSERT(HasAvailableFontFace(aFontFace)); + + mHasLoadingFontFacesIsDirty = true; + + if (aFontFace->Status() == FontFaceLoadStatus::Loading) { + CheckLoadingStarted(); + } else { + MOZ_ASSERT(aFontFace->Status() == FontFaceLoadStatus::Loaded || + aFontFace->Status() == FontFaceLoadStatus::Error); + // When a font finishes downloading, nsPresContext::UserFontSetUpdated + // will be called immediately afterwards to request a reflow of the + // relevant elements in the document. We want to wait until the reflow + // request has been done before the FontFaceSet is marked as Loaded so + // that we don't briefly set the FontFaceSet to Loaded and then Loading + // again once the reflow is pending. So we go around the event loop + // and call CheckLoadingFinished() after the reflow has been queued. + if (!mDelayedLoadCheck) { + mDelayedLoadCheck = true; + DispatchCheckLoadingFinishedAfterDelay(); + } + } +} + +void FontFaceSetImpl::DispatchCheckLoadingFinishedAfterDelay() { + 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. + set->AppendTask( + PostTraversalTask::DispatchFontFaceSetCheckLoadingFinishedAfterDelay( + this)); + return; + } + + DispatchToOwningThread( + "FontFaceSetImpl::DispatchCheckLoadingFinishedAfterDelay", + [self = RefPtr{this}]() { self->CheckLoadingFinishedAfterDelay(); }); +} + +void FontFaceSetImpl::CheckLoadingFinishedAfterDelay() { + RecursiveMutexAutoLock lock(mMutex); + mDelayedLoadCheck = false; + CheckLoadingFinished(); +} + +void FontFaceSetImpl::CheckLoadingStarted() { + gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked(); + RecursiveMutexAutoLock lock(mMutex); + + if (!HasLoadingFontFaces()) { + return; + } + + if (mStatus == FontFaceSetLoadStatus::Loading) { + // We have already dispatched a loading event and replaced mReady + // with a fresh, unresolved promise. + return; + } + + mStatus = FontFaceSetLoadStatus::Loading; + + if (IsOnOwningThread()) { + OnLoadingStarted(); + return; + } + + DispatchToOwningThread("FontFaceSetImpl::CheckLoadingStarted", + [self = RefPtr{this}]() { self->OnLoadingStarted(); }); +} + +void FontFaceSetImpl::OnLoadingStarted() { + RecursiveMutexAutoLock lock(mMutex); + if (mOwner) { + mOwner->DispatchLoadingEventAndReplaceReadyPromise(); + } +} + +void FontFaceSetImpl::UpdateHasLoadingFontFaces() { + RecursiveMutexAutoLock lock(mMutex); + mHasLoadingFontFacesIsDirty = false; + mHasLoadingFontFaces = false; + for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { + if (mNonRuleFaces[i].mFontFace->Status() == FontFaceLoadStatus::Loading) { + mHasLoadingFontFaces = true; + return; + } + } +} + +bool FontFaceSetImpl::HasLoadingFontFaces() { + RecursiveMutexAutoLock lock(mMutex); + if (mHasLoadingFontFacesIsDirty) { + UpdateHasLoadingFontFaces(); + } + return mHasLoadingFontFaces; +} + +bool FontFaceSetImpl::MightHavePendingFontLoads() { + // Check for FontFace objects in the FontFaceSet that are still loading. + return HasLoadingFontFaces(); +} + +void FontFaceSetImpl::CheckLoadingFinished() { + RecursiveMutexAutoLock lock(mMutex); + if (mDelayedLoadCheck) { + // Wait until the runnable posted in OnFontFaceStatusChanged calls us. + return; + } + + if (!ReadyPromiseIsPending()) { + // We've already resolved mReady (or set the flag to do that lazily) and + // dispatched the loadingdone/loadingerror events. + return; + } + + if (MightHavePendingFontLoads()) { + // We're not finished loading yet. + return; + } + + mStatus = FontFaceSetLoadStatus::Loaded; + + if (IsOnOwningThread()) { + OnLoadingFinished(); + return; + } + + DispatchToOwningThread( + "FontFaceSetImpl::CheckLoadingFinished", + [self = RefPtr{this}]() { self->OnLoadingFinished(); }); +} + +void FontFaceSetImpl::OnLoadingFinished() { + RecursiveMutexAutoLock lock(mMutex); + if (mOwner) { + mOwner->MaybeResolve(); + } +} + +void FontFaceSetImpl::RefreshStandardFontLoadPrincipal() { + RecursiveMutexAutoLock lock(mMutex); + mAllowedFontLoads.Clear(); + IncrementGeneration(false); +} + +// -- gfxUserFontSet +// ------------------------------------------------ + +already_AddRefed<gfxFontSrcPrincipal> +FontFaceSetImpl::GetStandardFontLoadPrincipal() const { + RecursiveMutexAutoLock lock(mMutex); + return RefPtr{mStandardFontLoadPrincipal}.forget(); +} + +void FontFaceSetImpl::RecordFontLoadDone(uint32_t aFontSize, + TimeStamp aDoneTime) { + mDownloadCount++; + mDownloadSize += aFontSize; + Telemetry::Accumulate(Telemetry::WEBFONT_SIZE, aFontSize / 1024); + + TimeStamp navStart = GetNavigationStartTimeStamp(); + TimeStamp zero; + if (navStart != zero) { + Telemetry::AccumulateTimeDelta(Telemetry::WEBFONT_DOWNLOAD_TIME_AFTER_START, + navStart, aDoneTime); + } +} + +void FontFaceSetImpl::DoRebuildUserFontSet() { MarkUserFontSetDirty(); } + +already_AddRefed<gfxUserFontEntry> FontFaceSetImpl::CreateUserFontEntry( + nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr) { + RefPtr<gfxUserFontEntry> entry = new FontFaceImpl::Entry( + this, std::move(aFontFaceSrcList), std::move(aAttr)); + return entry.forget(); +} + +void FontFaceSetImpl::ForgetLocalFaces() { + // We cannot hold our lock at the same time as the gfxUserFontFamily lock, so + // we need to make a copy of the table first. + nsTArray<RefPtr<gfxUserFontFamily>> fontFamilies; + { + RecursiveMutexAutoLock lock(mMutex); + fontFamilies.SetCapacity(mFontFamilies.Count()); + for (const auto& fam : mFontFamilies.Values()) { + fontFamilies.AppendElement(fam); + } + } + + for (const auto& fam : fontFamilies) { + ForgetLocalFace(fam); + } +} + +already_AddRefed<gfxUserFontFamily> FontFaceSetImpl::GetFamily( + const nsACString& aFamilyName) { + RecursiveMutexAutoLock lock(mMutex); + return gfxUserFontSet::GetFamily(aFamilyName); +} + +#undef LOG_ENABLED +#undef LOG |