Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "SharedFontList-impl.h"
#include "gfxPlatformFontList.h"
#include "gfxFontUtils.h"
#include "gfxFont.h"
#include "nsReadableUtils.h"
#include "prerror.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#define LOG_FONTLIST(args) \
MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
#define LOG_FONTLIST_ENABLED() \
MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
namespace mozilla {
namespace fontlist {
static double WSSDistance(const Face* aFace, const gfxFontStyle& aStyle) {
double stretchDist = StretchDistance(aFace->mStretch, aStyle.stretch);
double styleDist = StyleDistance(aFace->mStyle, aStyle.style);
double weightDist = WeightDistance(aFace->mWeight, aStyle.weight);
// Sanity-check that the distances are within the expected range
// (update if implementation of the distance functions is changed).
MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0);
MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0);
MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0);
// weight/style/stretch priority: stretch >> style >> weight
// so we multiply the stretch and style values to make them dominate
// the result
return stretchDist * kStretchFactor + styleDist * kStyleFactor +
weightDist * kWeightFactor;
}
void* Pointer::ToPtr(FontList* aFontList,
size_t aSize) const MOZ_NO_THREAD_SAFETY_ANALYSIS {
// On failure, we'll return null; callers need to handle this appropriately
// (e.g. via fallback).
void* result = nullptr;
if (IsNull()) {
return result;
}
// Ensure the list doesn't get replaced out from under us. Font-list rebuild
// happens on the main thread, so only non-main-thread callers need to lock
// it here.
bool isMainThread = NS_IsMainThread();
if (!isMainThread) {
gfxPlatformFontList::PlatformFontList()->Lock();
}
uint32_t blockIndex = Block();
// If the Pointer refers to a block we have not yet mapped in this process,
// we first need to retrieve new block handle(s) from the parent and update
// our mBlocks list.
auto& blocks = aFontList->mBlocks;
if (blockIndex >= blocks.Length()) {
if (MOZ_UNLIKELY(XRE_IsParentProcess())) {
// Shouldn't happen! A content process tried to pass a bad Pointer?
goto cleanup;
}
// If we're not on the main thread, we can't do the IPC involved in
// UpdateShmBlocks; just let the lookup fail for now.
if (!isMainThread) {
goto cleanup;
}
// UpdateShmBlocks can fail, if the parent has replaced the font list with
// a new generation. In that case we just return null, and whatever font
// the content process was trying to use will appear unusable for now. It's
// about to receive a notification of the new font list anyhow, at which
// point it will flush its caches and reflow everything, so the temporary
// failure of this font will be forgotten.
// UpdateShmBlocks will take the platform font-list lock during the update.
if (MOZ_UNLIKELY(!aFontList->UpdateShmBlocks(true))) {
goto cleanup;
}
MOZ_ASSERT(blockIndex < blocks.Length(), "failure in UpdateShmBlocks?");
// This is wallpapering bug 1667977; it's unclear if we will always survive
// this, as the content process may be unable to shape/render text if all
// font lookups are failing.
// In at least some cases, however, this can occur transiently while the
// font list is being rebuilt by the parent; content will then be notified
// that the list has changed, and should refresh everything successfully.
if (MOZ_UNLIKELY(blockIndex >= blocks.Length())) {
goto cleanup;
}
}
{
// Don't create a pointer that's outside what the block has allocated!
const auto& block = blocks[blockIndex];
if (MOZ_LIKELY(Offset() + aSize <= block->Allocated())) {
result = static_cast<char*>(block->Memory()) + Offset();
}
}
cleanup:
if (!isMainThread) {
gfxPlatformFontList::PlatformFontList()->Unlock();
}
return result;
}
void String::Assign(const nsACString& aString, FontList* aList) {
// We only assign to previously-empty strings; they are never modified
// after initial assignment.
MOZ_ASSERT(mPointer.IsNull());
mLength = aString.Length();
mPointer = aList->Alloc(mLength + 1);
auto* p = mPointer.ToArray<char>(aList, mLength);
std::memcpy(p, aString.BeginReading(), mLength);
p[mLength] = '\0';
}
Family::Family(FontList* aList, const InitData& aData)
: mFaceCount(0),
mKey(aList, aData.mKey),
mName(aList, aData.mName),
mCharacterMap(Pointer::Null()),
mFaces(Pointer::Null()),
mIndex(aData.mIndex),
mVisibility(aData.mVisibility),
mIsSimple(false),
mIsBundled(aData.mBundled),
mIsBadUnderlineFamily(aData.mBadUnderline),
mIsForceClassic(aData.mForceClassic),
mIsAltLocale(aData.mAltLocale) {}
class SetCharMapRunnable : public mozilla::Runnable {
public:
SetCharMapRunnable(uint32_t aListGeneration,
std::pair<uint32_t, bool> aFamilyIndex,
uint32_t aFaceIndex, gfxCharacterMap* aCharMap)
: Runnable("SetCharMapRunnable"),
mListGeneration(aListGeneration),
mFamilyIndex(aFamilyIndex),
mFaceIndex(aFaceIndex),
mCharMap(aCharMap) {}
NS_IMETHOD Run() override {
auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
if (!list || list->GetGeneration() != mListGeneration) {
return NS_OK;
}
dom::ContentChild::GetSingleton()->SendSetCharacterMap(
mListGeneration, mFamilyIndex.first, mFamilyIndex.second, mFaceIndex,
*mCharMap);
return NS_OK;
}
private:
uint32_t mListGeneration;
std::pair<uint32_t, bool> mFamilyIndex;
uint32_t mFaceIndex;
RefPtr<gfxCharacterMap> mCharMap;
};
void Face::SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap,
const Family* aFamily) {
if (!XRE_IsParentProcess()) {
Maybe<std::pair<uint32_t, bool>> familyIndex = aFamily->FindIndex(aList);
if (!familyIndex) {
NS_WARNING("Family index not found! Ignoring SetCharacterMap");
return;
}
const auto* faces = aFamily->Faces(aList);
uint32_t faceIndex = 0;
while (faceIndex < aFamily->NumFaces()) {
if (faces[faceIndex].ToPtr<Face>(aList) == this) {
break;
}
++faceIndex;
}
if (faceIndex >= aFamily->NumFaces()) {
NS_WARNING("Face not found in family! Ignoring SetCharacterMap");
return;
}
if (NS_IsMainThread()) {
dom::ContentChild::GetSingleton()->SendSetCharacterMap(
aList->GetGeneration(), familyIndex->first, familyIndex->second,
faceIndex, *aCharMap);
} else {
NS_DispatchToMainThread(new SetCharMapRunnable(
aList->GetGeneration(), familyIndex.value(), faceIndex, aCharMap));
}
return;
}
auto pfl = gfxPlatformFontList::PlatformFontList();
mCharacterMap = pfl->GetShmemCharMap(aCharMap);
}
void Family::AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces) {
MOZ_ASSERT(XRE_IsParentProcess());
if (mFaceCount > 0) {
// Already initialized!
return;
}
uint32_t count = aFaces.Length();
bool isSimple = false;
// A family is "simple" (i.e. simplified style selection may be used instead
// of the full CSS font-matching algorithm) if there is at maximum one normal,
// bold, italic, and bold-italic face; in this case, they are stored at known
// positions in the mFaces array.
const Face::InitData* slots[4] = {nullptr, nullptr, nullptr, nullptr};
if (count >= 2 && count <= 4) {
// Check if this can be treated as a "simple" family
isSimple = true;
for (const auto& f : aFaces) {
if (!f.mWeight.IsSingle() || !f.mStretch.IsSingle() ||
!f.mStyle.IsSingle()) {
isSimple = false;
break;
}
if (!f.mStretch.Min().IsNormal()) {
isSimple = false;
break;
}
// Figure out which slot (0-3) this face belongs in
size_t slot = 0;
static_assert((kBoldMask | kItalicMask) == 0b11, "bad bold/italic bits");
if (f.mWeight.Min().IsBold()) {
slot |= kBoldMask;
}
if (f.mStyle.Min().IsItalic() || f.mStyle.Min().IsOblique()) {
slot |= kItalicMask;
}
if (slots[slot]) {
// More than one face mapped to the same slot - not a simple family!
isSimple = false;
break;
}
slots[slot] = &f;
}
if (isSimple) {
// Ensure all 4 slots will exist, even if some are empty.
count = 4;
}
}
// Allocate space for the face records, and initialize them.
// coverity[suspicious_sizeof]
Pointer p = aList->Alloc(count * sizeof(Pointer));
auto* facePtrs = p.ToArray<Pointer>(aList, count);
for (size_t i = 0; i < count; i++) {
if (isSimple && !slots[i]) {
facePtrs[i] = Pointer::Null();
} else {
const auto* initData = isSimple ? slots[i] : &aFaces[i];
Pointer fp = aList->Alloc(sizeof(Face));
auto* face = fp.ToPtr<Face>(aList);
(void)new (face) Face(aList, *initData);
facePtrs[i] = fp;
if (initData->mCharMap) {
face->SetCharacterMap(aList, initData->mCharMap, this);
}
}
}
mIsSimple = isSimple;
mFaces = p;
mFaceCount.store(count);
if (LOG_FONTLIST_ENABLED()) {
const nsCString& fam = DisplayName().AsString(aList);
for (unsigned j = 0; j < aFaces.Length(); j++) {
nsAutoCString weight, style, stretch;
aFaces[j].mWeight.ToString(weight);
aFaces[j].mStyle.ToString(style);
aFaces[j].mStretch.ToString(stretch);
LOG_FONTLIST(
("(shared-fontlist) family (%s) added face (%s) index %u, weight "
"%s, style %s, stretch %s",
fam.get(), aFaces[j].mDescriptor.get(), aFaces[j].mIndex,
weight.get(), style.get(), stretch.get()));
}
}
}
bool Family::FindAllFacesForStyleInternal(FontList* aList,
const gfxFontStyle& aStyle,
nsTArray<Face*>& aFaceList) const {
MOZ_ASSERT(aFaceList.IsEmpty());
if (!IsInitialized()) {
return false;
}
Pointer* facePtrs = Faces(aList);
if (!facePtrs) {
return false;
}
// Depending on the kind of family, we have to do varying amounts of work
// to figure out what face(s) to use for the requested style properties.
// If the family has only one face, we simply use it; no further style
// checking needed. (However, for bitmap fonts we may still need to check
// whether the size is acceptable.)
if (NumFaces() == 1) {
MOZ_ASSERT(!facePtrs[0].IsNull());
auto* face = facePtrs[0].ToPtr<Face>(aList);
if (face && face->HasValidDescriptor()) {
aFaceList.AppendElement(face);
#ifdef MOZ_WIDGET_GTK
if (face->mSize) {
return true;
}
#endif
}
return false;
}
// Most families are "simple", having just Regular/Bold/Italic/BoldItalic,
// or some subset of these. In this case, we have exactly 4 entries in
// mAvailableFonts, stored in the above order; note that some of the entries
// may be nullptr. We can then pick the required entry based on whether the
// request is for bold or non-bold, italic or non-italic, without running
// the more complex matching algorithm used for larger families with many
// weights and/or widths.
if (mIsSimple) {
// Family has no more than the "standard" 4 faces, at fixed indexes;
// calculate which one we want.
// Note that we cannot simply return it as not all 4 faces are necessarily
// present.
bool wantBold = aStyle.weight.IsBold();
bool wantItalic = !aStyle.style.IsNormal();
uint8_t faceIndex =
(wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0);
// If the desired style is available, use it directly.
auto* face = facePtrs[faceIndex].ToPtr<Face>(aList);
if (face && face->HasValidDescriptor()) {
aFaceList.AppendElement(face);
#ifdef MOZ_WIDGET_GTK
if (face->mSize) {
return true;
}
#endif
return false;
}
// Order to check fallback faces in a simple family, depending on the
// requested style.
static const uint8_t simpleFallbacks[4][3] = {
{kBoldFaceIndex, kItalicFaceIndex,
kBoldItalicFaceIndex}, // fallback sequence for Regular
{kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold
{kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic
{kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic
};
const uint8_t* order = simpleFallbacks[faceIndex];
for (uint8_t trial = 0; trial < 3; ++trial) {
// check remaining faces in order of preference to find the first that
// actually exists
face = facePtrs[order[trial]].ToPtr<Face>(aList);
if (face && face->HasValidDescriptor()) {
aFaceList.AppendElement(face);
#ifdef MOZ_WIDGET_GTK
if (face->mSize) {
return true;
}
#endif
return false;
}
}
// We can only reach here if we failed to resolve the face pointer, which
// can happen if we're on a stylo thread and caught the font list being
// updated; in that case we just fail quietly and let font fallback do
// something for the time being.
return false;
}
// Pick the font(s) that are closest to the desired weight, style, and
// stretch. Iterate over all fonts, measuring the weight/style distance.
// Because of unicode-range values, there may be more than one font for a
// given but the 99% use case is only a single font entry per
// weight/style/stretch distance value. To optimize this, only add entries
// to the matched font array when another entry already has the same
// weight/style/stretch distance and add the last matched font entry. For
// normal platform fonts with a single font entry for each
// weight/style/stretch combination, only the last matched font entry will
// be added.
double minDistance = INFINITY;
Face* matched = nullptr;
// Keep track of whether we've included any non-scalable font resources in
// the selected set.
bool anyNonScalable = false;
for (uint32_t i = 0; i < NumFaces(); i++) {
auto* face = facePtrs[i].ToPtr<Face>(aList);
if (face) {
// weight/style/stretch priority: stretch >> style >> weight
double distance = WSSDistance(face, aStyle);
if (distance < minDistance) {
matched = face;
if (!aFaceList.IsEmpty()) {
aFaceList.Clear();
}
minDistance = distance;
} else if (distance == minDistance) {
if (matched) {
aFaceList.AppendElement(matched);
#ifdef MOZ_WIDGET_GTK
if (matched->mSize) {
anyNonScalable = true;
}
#endif
}
matched = face;
}
}
}
MOZ_ASSERT(matched, "didn't match a font within a family");
if (matched) {
aFaceList.AppendElement(matched);
#ifdef MOZ_WIDGET_GTK
if (matched->mSize) {
anyNonScalable = true;
}
#endif
}
return anyNonScalable;
}
void Family::FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle,
nsTArray<Face*>& aFaceList,
bool aIgnoreSizeTolerance) const {
#ifdef MOZ_WIDGET_GTK
bool anyNonScalable =
#else
Unused <<
#endif
FindAllFacesForStyleInternal(aList, aStyle, aFaceList);
#ifdef MOZ_WIDGET_GTK
// aFaceList now contains whatever faces are the best style match for
// the requested style. If specifically-sized bitmap faces are supported,
// we need to additionally filter the list to choose the appropriate size.
//
// It would be slightly more efficient to integrate this directly into the
// face-selection algorithm above, but it's a rare case that doesn't apply
// at all to most font families.
//
// Currently we only support pixel-sized bitmap font faces on Linux/Gtk (i.e.
// when using the gfxFcPlatformFontList implementation), so this filtering is
// not needed on other platforms.
//
// (Note that color-bitmap emoji fonts like Apple Color Emoji or Noto Color
// Emoji don't count here; they package multiple bitmap sizes into a single
// OpenType wrapper, so they appear as a single "scalable" face in our list.)
if (anyNonScalable) {
uint16_t best = 0;
gfxFloat dist = 0.0;
for (const auto& f : aFaceList) {
if (f->mSize == 0) {
// Scalable face; no size distance to compute.
continue;
}
gfxFloat d = fabs(gfxFloat(f->mSize) - aStyle.size);
if (!aIgnoreSizeTolerance && (d * 5.0 > f->mSize)) {
continue; // Too far from the requested size, ignore.
}
// If we haven't found a "best" bitmap size yet, or if this is a better
// match, remember it.
if (!best || d < dist) {
best = f->mSize;
dist = d;
}
}
// Discard all faces except the chosen "best" size; or if no pixel size was
// chosen, all except scalable faces.
// This may eliminate *all* faces in the family, if all were bitmaps and
// none was a good enough size match, in which case we'll fall back to the
// next font-family name.
aFaceList.RemoveElementsBy([=](const auto& e) { return e->mSize != best; });
}
#endif
}
Face* Family::FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle,
bool aIgnoreSizeTolerance) const {
AutoTArray<Face*, 4> faces;
FindAllFacesForStyle(aList, aStyle, faces, aIgnoreSizeTolerance);
return faces.IsEmpty() ? nullptr : faces[0];
}
void Family::SearchAllFontsForChar(FontList* aList,
GlobalFontMatch* aMatchData) {
auto* charmap = mCharacterMap.ToPtr<const SharedBitSet>(aList);
if (!charmap) {
// If the face list is not yet initialized, or if character maps have
// not been loaded, go ahead and do this now (by sending a message to the
// parent process, if we're running in a child).
// After this, all faces should have their mCharacterMap set up, and the
// family's mCharacterMap should also be set; but in the code below we
// don't assume this all succeeded, so it still checks.
if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(this,
true)) {
return;
}
charmap = mCharacterMap.ToPtr<const SharedBitSet>(aList);
}
if (charmap && !charmap->test(aMatchData->mCh)) {
return;
}
uint32_t numFaces = NumFaces();
uint32_t charMapsLoaded = 0; // number of faces whose charmap is loaded
Pointer* facePtrs = Faces(aList);
if (!facePtrs) {
return;
}
for (uint32_t i = 0; i < numFaces; i++) {
auto* face = facePtrs[i].ToPtr<Face>(aList);
if (!face) {
continue;
}
MOZ_ASSERT(face->HasValidDescriptor());
// Get the face's character map, if available (may be null!)
charmap = face->mCharacterMap.ToPtr<const SharedBitSet>(aList);
if (charmap) {
++charMapsLoaded;
}
// Check style distance if the char is supported, or if charmap not known
// (so that we don't trigger cmap-loading for faces that would be a worse
// match than what we've already found).
if (!charmap || charmap->test(aMatchData->mCh)) {
double distance = WSSDistance(face, aMatchData->mStyle);
if (distance < aMatchData->mMatchDistance) {
// It's a better style match: get a fontEntry, and if we haven't
// already checked character coverage, do it now (note that
// HasCharacter() will trigger loading the fontEntry's cmap, if
// needed).
RefPtr<gfxFontEntry> fe =
gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry(face,
this);
if (!fe) {
continue;
}
if (!charmap && !fe->HasCharacter(aMatchData->mCh)) {
continue;
}
if (aMatchData->mPresentation != eFontPresentation::Any) {
RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle);
if (!font) {
continue;
}
bool hasColorGlyph =
font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh);
if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) {
distance += kPresentationMismatch;
if (distance >= aMatchData->mMatchDistance) {
continue;
}
}
}
aMatchData->mBestMatch = fe;
aMatchData->mMatchDistance = distance;
aMatchData->mMatchedSharedFamily = this;
}
}
}
if (mCharacterMap.IsNull() && charMapsLoaded == numFaces) {
SetupFamilyCharMap(aList);
}
}
void Family::SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces) {
if (aFaces.Length() >= 2 && aFaces.Length() <= 4) {
// Check whether the faces meet the criteria for a "simple" family: no more
// than one each of Regular, Bold, Italic, BoldItalic styles. If so, store
// them at the appropriate slots in mFaces and set the mIsSimple flag to
// accelerate font-matching.
bool isSimple = true;
Pointer slots[4] = {Pointer::Null(), Pointer::Null(), Pointer::Null(),
Pointer::Null()};
for (const Pointer& fp : aFaces) {
auto* f = fp.ToPtr<const Face>(aList);
if (!f->mWeight.IsSingle() || !f->mStyle.IsSingle() ||
!f->mStretch.IsSingle()) {
isSimple = false;
break;
}
if (!f->mStretch.Min().IsNormal()) {
isSimple = false;
break;
}
size_t slot = 0;
if (f->mWeight.Min().IsBold()) {
slot |= kBoldMask;
}
if (f->mStyle.Min().IsItalic() || f->mStyle.Min().IsOblique()) {
slot |= kItalicMask;
}
if (!slots[slot].IsNull()) {
isSimple = false;
break;
}
slots[slot] = fp;
}
if (isSimple) {
size_t size = 4 * sizeof(Pointer);
mFaces = aList->Alloc(size);
memcpy(mFaces.ToPtr(aList, size), slots, size);
mFaceCount.store(4);
mIsSimple = true;
return;
}
}
size_t size = aFaces.Length() * sizeof(Pointer);
mFaces = aList->Alloc(size);
memcpy(mFaces.ToPtr(aList, size), aFaces.Elements(), size);
mFaceCount.store(aFaces.Length());
}
void Family::SetupFamilyCharMap(FontList* aList) {
// Set the character map of the family to the union of all the face cmaps,
// to allow font fallback searches to more rapidly reject the family.
if (!mCharacterMap.IsNull()) {
return;
}
if (!XRE_IsParentProcess()) {
// |this| could be a Family record in either the Families() or Aliases()
// arrays; FindIndex will map it back to its index and which array.
Maybe<std::pair<uint32_t, bool>> index = FindIndex(aList);
if (!index) {
NS_WARNING("Family index not found! Ignoring SetupFamilyCharMap");
return;
}
if (NS_IsMainThread()) {
dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap(
aList->GetGeneration(), index->first, index->second);
return;
}
NS_DispatchToMainThread(NS_NewRunnableFunction(
"SetupFamilyCharMap callback",
[gen = aList->GetGeneration(), idx = index->first,
alias = index->second] {
dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap(gen, idx,
alias);
}));
return;
}
gfxSparseBitSet familyMap;
Pointer firstMapShmPointer;
const SharedBitSet* firstMap = nullptr;
bool merged = false;
Pointer* faces = Faces(aList);
if (!faces) {
return;
}
for (size_t i = 0; i < NumFaces(); i++) {
auto* f = faces[i].ToPtr<const Face>(aList);
if (!f) {
continue; // Skip missing face (in an incomplete "simple" family)
}
auto* faceMap = f->mCharacterMap.ToPtr<const SharedBitSet>(aList);
if (!faceMap) {
continue; // If there's a face where setting up the cmap failed, we skip
// it as unusable.
}
if (!firstMap) {
firstMap = faceMap;
firstMapShmPointer = f->mCharacterMap;
} else if (faceMap != firstMap) {
if (!merged) {
familyMap.Union(*firstMap);
merged = true;
}
familyMap.Union(*faceMap);
}
}
// If we created a merged cmap, we need to save that on the family; or if we
// found no usable cmaps at all, we need to store the empty familyMap so that
// we won't repeatedly attempt this for an unusable family.
if (merged || firstMapShmPointer.IsNull()) {
mCharacterMap =
gfxPlatformFontList::PlatformFontList()->GetShmemCharMap(&familyMap);
} else {
// If all [usable] faces had the same cmap, we can just share it.
mCharacterMap = firstMapShmPointer;
}
}
Maybe<std::pair<uint32_t, bool>> Family::FindIndex(FontList* aList) const {
const auto* start = aList->Families();
const auto* end = start + aList->NumFamilies();
if (this >= start && this < end) {
uint32_t index = this - start;
MOZ_RELEASE_ASSERT(start + index == this, "misaligned Family ptr!");
return Some(std::pair(index, false));
}
start = aList->AliasFamilies();
end = start + aList->NumAliases();
if (this >= start && this < end) {
uint32_t index = this - start;
MOZ_RELEASE_ASSERT(start + index == this, "misaligned AliasFamily ptr!");
return Some(std::pair(index, true));
}
return Nothing();
}
FontList::FontList(uint32_t aGeneration) {
if (XRE_IsParentProcess()) {
// Create the initial shared block, and initialize Header
if (AppendShmBlock(SHM_BLOCK_SIZE)) {
Header& header = GetHeader();
header.mBlockHeader.mAllocated.store(sizeof(Header));
header.mGeneration = aGeneration;
header.mFamilyCount = 0;
header.mBlockCount.store(1);
header.mAliasCount.store(0);
header.mLocalFaceCount.store(0);
header.mFamilies = Pointer::Null();
header.mAliases = Pointer::Null();
header.mLocalFaces = Pointer::Null();
} else {
MOZ_CRASH("parent: failed to initialize FontList");
}
} else {
// Initialize using the list of shmem blocks passed by the parent via
// SetXPCOMProcessAttributes.
auto& blocks = dom::ContentChild::GetSingleton()->SharedFontListBlocks();
for (auto& handle : blocks) {
auto newShm = MakeUnique<base::SharedMemory>();
if (!newShm->IsHandleValid(handle)) {
// Bail out and let UpdateShmBlocks try to do its thing below.
break;
}
if (!newShm->SetHandle(std::move(handle), true)) {
MOZ_CRASH("failed to set shm handle");
}
if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
}
uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize;
MOZ_ASSERT(size >= SHM_BLOCK_SIZE);
if (size != SHM_BLOCK_SIZE) {
newShm->Unmap();
if (!newShm->Map(size) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
}
}
mBlocks.AppendElement(new ShmBlock(std::move(newShm)));
}
blocks.Clear();
// Update in case of any changes since the initial message was sent.
for (unsigned retryCount = 0; retryCount < 3; ++retryCount) {
if (UpdateShmBlocks(false)) {
return;
}
// The only reason for UpdateShmBlocks to fail is if the parent recreated
// the list after we read its first block, but before we finished getting
// them all, and so the generation check failed on a subsequent request.
// Discarding whatever we've got and retrying should get us a new,
// consistent set of memory blocks in this case. If this doesn't work
// after a couple of retries, bail out.
DetachShmBlocks();
}
NS_WARNING("child: failed to initialize shared FontList");
}
}
FontList::~FontList() { DetachShmBlocks(); }
FontList::Header& FontList::GetHeader() const MOZ_NO_THREAD_SAFETY_ANALYSIS {
// We only need to lock if we're not on the main thread.
bool isMainThread = NS_IsMainThread();
if (!isMainThread) {
gfxPlatformFontList::PlatformFontList()->Lock();
}
// It's invalid to try and access this before the first block exists.
MOZ_ASSERT(mBlocks.Length() > 0);
auto& result = *static_cast<Header*>(mBlocks[0]->Memory());
if (!isMainThread) {
gfxPlatformFontList::PlatformFontList()->Unlock();
}
return result;
}
bool FontList::AppendShmBlock(uint32_t aSizeNeeded) {
MOZ_ASSERT(XRE_IsParentProcess());
uint32_t size = std::max(aSizeNeeded, SHM_BLOCK_SIZE);
auto newShm = MakeUnique<base::SharedMemory>();
if (!newShm->CreateFreezeable(size)) {
MOZ_CRASH("failed to create shared memory");
return false;
}
if (!newShm->Map(size) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
return false;
}
auto readOnly = MakeUnique<base::SharedMemory>();
if (!newShm->ReadOnlyCopy(readOnly.get())) {
MOZ_CRASH("failed to create read-only copy");
return false;
}
ShmBlock* block = new ShmBlock(std::move(newShm));
block->StoreAllocated(sizeof(BlockHeader));
block->BlockSize() = size;
mBlocks.AppendElement(block);
GetHeader().mBlockCount.store(mBlocks.Length());
mReadOnlyShmems.AppendElement(std::move(readOnly));
// We don't need to broadcast the addition of the initial block,
// because child processes can't have initialized their list at all
// prior to the first block being set up.
if (mBlocks.Length() > 1) {
if (NS_IsMainThread()) {
dom::ContentParent::BroadcastShmBlockAdded(GetGeneration(),
mBlocks.Length() - 1);
} else {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"ShmBlockAdded callback",
[generation = GetGeneration(), index = mBlocks.Length() - 1] {
dom::ContentParent::BroadcastShmBlockAdded(generation, index);
}));
}
}
return true;
}
void FontList::ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex,
base::SharedMemoryHandle aHandle) {
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_ASSERT(mBlocks.Length() > 0);
auto newShm = MakeUnique<base::SharedMemory>();
if (!newShm->IsHandleValid(aHandle)) {
return;
}
if (!newShm->SetHandle(std::move(aHandle), true)) {
MOZ_CRASH("failed to set shm handle");
}
if (aIndex != mBlocks.Length()) {
return;
}
if (aGeneration != GetGeneration()) {
return;
}
if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
}
uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize;
MOZ_ASSERT(size >= SHM_BLOCK_SIZE);
if (size != SHM_BLOCK_SIZE) {
newShm->Unmap();
if (!newShm->Map(size) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
}
}
mBlocks.AppendElement(new ShmBlock(std::move(newShm)));
}
void FontList::DetachShmBlocks() {
for (auto& i : mBlocks) {
i->mShmem = nullptr;
}
mBlocks.Clear();
mReadOnlyShmems.Clear();
}
FontList::ShmBlock* FontList::GetBlockFromParent(uint32_t aIndex) {
MOZ_ASSERT(!XRE_IsParentProcess());
// If we have no existing blocks, we don't want a generation check yet;
// the header in the first block will define the generation of this list
uint32_t generation = aIndex == 0 ? 0 : GetGeneration();
base::SharedMemoryHandle handle = base::SharedMemory::NULLHandle();
if (!dom::ContentChild::GetSingleton()->SendGetFontListShmBlock(
generation, aIndex, &handle)) {
return nullptr;
}
auto newShm = MakeUnique<base::SharedMemory>();
if (!newShm->IsHandleValid(handle)) {
return nullptr;
}
if (!newShm->SetHandle(std::move(handle), true)) {
MOZ_CRASH("failed to set shm handle");
}
if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
}
uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize;
MOZ_ASSERT(size >= SHM_BLOCK_SIZE);
if (size != SHM_BLOCK_SIZE) {
newShm->Unmap();
if (!newShm->Map(size) || !newShm->memory()) {
MOZ_CRASH("failed to map shared memory");
}
}
return new ShmBlock(std::move(newShm));
}
// We don't take the lock when called from the constructor, so disable thread-
// safety analysis here.
bool FontList::UpdateShmBlocks(bool aMustLock) MOZ_NO_THREAD_SAFETY_ANALYSIS {
MOZ_ASSERT(!XRE_IsParentProcess());
if (aMustLock) {
gfxPlatformFontList::PlatformFontList()->Lock();
}
bool result = true;
while (!mBlocks.Length() || mBlocks.Length() < GetHeader().mBlockCount) {
ShmBlock* newBlock = GetBlockFromParent(mBlocks.Length());
if (!newBlock) {
result = false;
break;
}
mBlocks.AppendElement(newBlock);
}
if (aMustLock) {
gfxPlatformFontList::PlatformFontList()->Unlock();
}
return result;
}
void FontList::ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks,
base::ProcessId aPid) {
MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length());
for (auto& shmem : mReadOnlyShmems) {
auto handle = shmem->CloneHandle();
if (!handle) {
// If something went wrong here, we just bail out; the child will need to
// request the blocks as needed, at some performance cost. (Although in
// practice this may mean resources are so constrained the child process
// isn't really going to work at all. But that's not our problem here.)
aBlocks->Clear();
return;
}
aBlocks->AppendElement(std::move(handle));
}
}
base::SharedMemoryHandle FontList::ShareBlockToProcess(uint32_t aIndex,
base::ProcessId aPid) {
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length());
MOZ_RELEASE_ASSERT(aIndex < mReadOnlyShmems.Length());
return mReadOnlyShmems[aIndex]->CloneHandle();
}
Pointer FontList::Alloc(uint32_t aSize) {
// Only the parent process does allocation.
MOZ_ASSERT(XRE_IsParentProcess());
// 4-byte alignment is good enough for anything we allocate in the font list,
// as our "Pointer" (block index/offset) is a 32-bit value even on x64.
auto align = [](uint32_t aSize) -> size_t { return (aSize + 3u) & ~3u; };
aSize = align(aSize);
int32_t blockIndex = -1;
uint32_t curAlloc, size;
if (aSize < SHM_BLOCK_SIZE - sizeof(BlockHeader)) {
// Try to allocate in the most recently added block first, as this is
// highly likely to succeed; if not, try earlier blocks (to fill gaps).
const int32_t blockCount = mBlocks.Length();
for (blockIndex = blockCount - 1; blockIndex >= 0; --blockIndex) {
size = mBlocks[blockIndex]->BlockSize();
curAlloc = mBlocks[blockIndex]->Allocated();
if (size - curAlloc >= aSize) {
break;
}
}
}
if (blockIndex < 0) {
// Couldn't find enough space (or the requested size is too large to use
// a part of a block): create a new block.
if (!AppendShmBlock(aSize + sizeof(BlockHeader))) {
return Pointer::Null();
}
blockIndex = mBlocks.Length() - 1;
curAlloc = mBlocks[blockIndex]->Allocated();
}
// We've found a block; allocate space from it, and return
mBlocks[blockIndex]->StoreAllocated(curAlloc + aSize);
return Pointer(blockIndex, curAlloc);
}
void FontList::SetFamilyNames(nsTArray<Family::InitData>& aFamilies) {
// Only the parent process should ever assign the list of families.
MOZ_ASSERT(XRE_IsParentProcess());
Header& header = GetHeader();
MOZ_ASSERT(!header.mFamilyCount);
gfxPlatformFontList::PlatformFontList()->ApplyWhitelist(aFamilies);
aFamilies.Sort();
size_t count = aFamilies.Length();
// Any font resources with an empty family-name will have been collected in
// a family with empty name, and sorted to the start of the list. Such fonts
// are not generally usable, or recognized as "installed", so we drop them.
if (count > 1 && aFamilies[0].mKey.IsEmpty()) {
aFamilies.RemoveElementAt(0);
--count;
}
// Check for duplicate family entries (can occur if there is a bundled font
// that has the same name as a system-installed one); in this case we keep
// the bundled one as it will always be exposed.
if (count > 1) {
for (size_t i = 1; i < count; ++i) {
if (aFamilies[i].mKey.Equals(aFamilies[i - 1].mKey)) {
// Decide whether to discard the current entry or the preceding one
size_t discard =
aFamilies[i].mBundled && !aFamilies[i - 1].mBundled ? i - 1 : i;
aFamilies.RemoveElementAt(discard);
--count;
--i;
}
}
}
header.mFamilies = Alloc(count * sizeof(Family));
if (header.mFamilies.IsNull()) {
return;
}
// We can't call Families() here because the mFamilyCount field has not yet
// been set!
auto* families = header.mFamilies.ToArray<Family>(this, count);
for (size_t i = 0; i < count; i++) {
(void)new (&families[i]) Family(this, aFamilies[i]);
LOG_FONTLIST(("(shared-fontlist) family %u (%s)", (unsigned)i,
aFamilies[i].mName.get()));
}
header.mFamilyCount = count;
}
void FontList::SetAliases(
nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable) {
MOZ_ASSERT(XRE_IsParentProcess());
Header& header = GetHeader();
// Build an array of Family::InitData records based on the entries in
// aAliasTable, then sort them and store into the fontlist.
nsTArray<Family::InitData> aliasArray;
aliasArray.SetCapacity(aAliasTable.Count());
for (const auto& entry : aAliasTable) {
aliasArray.AppendElement(Family::InitData(
entry.GetKey(), entry.GetData()->mBaseFamily, entry.GetData()->mIndex,
entry.GetData()->mVisibility, entry.GetData()->mBundled,
entry.GetData()->mBadUnderline, entry.GetData()->mForceClassic, true));
}
aliasArray.Sort();
size_t count = aliasArray.Length();
// Drop any entry with empty family-name as being unusable.
if (count && aliasArray[0].mKey.IsEmpty()) {
aliasArray.RemoveElementAt(0);
--count;
}
if (count < header.mAliasCount) {
// This shouldn't happen, but handle it safely by just bailing out.
NS_WARNING("cannot reduce number of aliases");
return;
}
fontlist::Pointer ptr = Alloc(count * sizeof(Family));
auto* aliases = ptr.ToArray<Family>(this, count);
for (size_t i = 0; i < count; i++) {
(void)new (&aliases[i]) Family(this, aliasArray[i]);
LOG_FONTLIST(("(shared-fontlist) alias family %u (%s: %s)", (unsigned)i,
aliasArray[i].mKey.get(), aliasArray[i].mName.get()));
aliases[i].SetFacePtrs(this, aAliasTable.Get(aliasArray[i].mKey)->mFaces);
if (LOG_FONTLIST_ENABLED()) {
const auto& faces = aAliasTable.Get(aliasArray[i].mKey)->mFaces;
for (unsigned j = 0; j < faces.Length(); j++) {
auto* face = faces[j].ToPtr<const Face>(this);
const nsCString& desc = face->mDescriptor.AsString(this);
nsAutoCString weight, style, stretch;
face->mWeight.ToString(weight);
face->mStyle.ToString(style);
face->mStretch.ToString(stretch);
LOG_FONTLIST(
("(shared-fontlist) face (%s) index %u, weight %s, style %s, "
"stretch %s",
desc.get(), face->mIndex, weight.get(), style.get(),
stretch.get()));
}
}
}
// Set the pointer before the count, so that any other process trying to read
// will not risk out-of-bounds access to the array.
header.mAliases = ptr;
header.mAliasCount.store(count);
}
void FontList::SetLocalNames(
nsTHashMap<nsCStringHashKey, LocalFaceRec::InitData>& aLocalNameTable) {
MOZ_ASSERT(XRE_IsParentProcess());
Header& header = GetHeader();
if (header.mLocalFaceCount > 0) {
return; // already been done!
}
auto faceArray = ToTArray<nsTArray<nsCString>>(aLocalNameTable.Keys());
faceArray.Sort();
size_t count = faceArray.Length();
Family* families = Families();
fontlist::Pointer ptr = Alloc(count * sizeof(LocalFaceRec));
auto* faces = ptr.ToArray<LocalFaceRec>(this, count);
for (size_t i = 0; i < count; i++) {
(void)new (&faces[i]) LocalFaceRec();
const auto& rec = aLocalNameTable.Get(faceArray[i]);
faces[i].mKey.Assign(faceArray[i], this);
// Local face name records will refer to the canonical family name; we don't
// need to search aliases here.
const auto* family = FindFamily(rec.mFamilyName, /*aPrimaryNameOnly*/ true);
if (!family) {
// Skip this record if the family was excluded by the font whitelist pref.
continue;
}
faces[i].mFamilyIndex = family - families;
if (rec.mFaceIndex == uint32_t(-1)) {
// The InitData record contains an mFaceDescriptor rather than an index,
// so now we need to look for the appropriate index in the family.
faces[i].mFaceIndex = 0;
const Pointer* faceList =
static_cast<const Pointer*>(family->Faces(this));
for (uint32_t j = 0; j < family->NumFaces(); j++) {
if (!faceList[j].IsNull()) {
auto* f = faceList[j].ToPtr<const Face>(this);
if (f && rec.mFaceDescriptor == f->mDescriptor.AsString(this)) {
faces[i].mFaceIndex = j;
break;
}
}
}
} else {
faces[i].mFaceIndex = rec.mFaceIndex;
}
}
header.mLocalFaces = ptr;
header.mLocalFaceCount.store(count);
}
nsCString FontList::LocalizedFamilyName(const Family* aFamily) {
// If the given family was created for an alternate locale or legacy name,
// search for a standard family that corresponds to it. This is a linear
// search of the font list, but (a) this is only used to show names in
// Preferences, so is not performance-critical for layout etc.; and (b) few
// such family names are normally present anyway, the vast majority of fonts
// just have a single family name and we return it directly.
if (aFamily->IsAltLocaleFamily()) {
// Currently only the Windows backend actually does this; on other systems,
// the family index is unused and will be kNoIndex for all fonts.
if (aFamily->Index() != Family::kNoIndex) {
const Family* families = Families();
for (uint32_t i = 0; i < NumFamilies(); ++i) {
if (families[i].Index() == aFamily->Index() &&
families[i].IsBundled() == aFamily->IsBundled() &&
!families[i].IsAltLocaleFamily()) {
return families[i].DisplayName().AsString(this);
}
}
}
}
// For standard families (or if we failed to find the expected standard
// family for some reason), just return the DisplayName.
return aFamily->DisplayName().AsString(this);
}
Family* FontList::FindFamily(const nsCString& aName, bool aPrimaryNameOnly) {
struct FamilyNameComparator {
FamilyNameComparator(FontList* aList, const nsCString& aTarget)
: mList(aList), mTarget(aTarget) {}
int operator()(const Family& aVal) const {
return Compare(mTarget,
nsDependentCString(aVal.Key().BeginReading(mList)));
}
private:
FontList* mList;
const nsCString& mTarget;
};
const Header& header = GetHeader();
Family* families = Families();
if (!families) {
return nullptr;
}
size_t match;
if (BinarySearchIf(families, 0, header.mFamilyCount,
FamilyNameComparator(this, aName), &match)) {
return &families[match];
}
if (aPrimaryNameOnly) {
return nullptr;
}
if (header.mAliasCount) {
Family* aliases = AliasFamilies();
size_t match;
if (aliases && BinarySearchIf(aliases, 0, header.mAliasCount,
FamilyNameComparator(this, aName), &match)) {
return &aliases[match];
}
}
#ifdef XP_WIN
// For Windows only, because of how DWrite munges font family names in some
// cases (see
// and discussion on the OpenType list), try stripping a possible style-name
// suffix from the end of the requested family name.
// After the deferred font loader has finished, this is no longer needed as
// the "real" family names will have been found in AliasFamilies() above.
if (aName.Contains(' ')) {
auto pfl = gfxPlatformFontList::PlatformFontList();
pfl->mLock.AssertCurrentThreadIn();
if (header.mAliasCount) {
// Aliases have been fully loaded by the parent process, so just discard
// any stray mAliasTable and mLocalNameTable entries from earlier calls
// to this code, and return.
pfl->mAliasTable.Clear();
pfl->mLocalNameTable.Clear();
return nullptr;
}
// Do we already have an aliasData record for this name? If so, we just
// return its base family.
if (auto lookup = pfl->mAliasTable.Lookup(aName)) {
return FindFamily(lookup.Data()->mBaseFamily, true);
}
// Strip the style suffix (after last space in the name) to get a "base"
// family name.
const char* data = aName.BeginReading();
int32_t index = aName.Length();
while (--index > 0) {
if (data[index] == ' ') {
break;
}
}
if (index <= 0) {
return nullptr;
}
nsAutoCString base(Substring(aName, 0, index));
if (BinarySearchIf(families, 0, header.mFamilyCount,
FamilyNameComparator(this, base), &match)) {
// This may be a possible base family to satisfy the search; call
// ReadFaceNamesForFamily and see if the desired name ends up in
// mAliasTable.
// Note that ReadFaceNamesForFamily may store entries in mAliasTable
// (and mLocalNameTable), but if this is happening in a content
// process (which is the common case) those entries will not be saved
// into the shared font list; they're just used here until the "real"
// alias list is ready, then discarded.
Family* baseFamily = &families[match];
pfl->ReadFaceNamesForFamily(baseFamily, false);
if (auto lookup = pfl->mAliasTable.Lookup(aName)) {
if (lookup.Data()->mFaces.Length() != baseFamily->NumFaces()) {
// If the alias family doesn't have all the faces of the base family,
// then style matching may end up resolving to a face that isn't
// supposed to be available in the legacy styled family. To ensure
// such mis-styling will get fixed, we start the async font info
// loader (if it hasn't yet been triggered), which will pull in the
// full metadata we need and then force a reflow.
pfl->InitOtherFamilyNames(/* aDeferOtherFamilyNamesLoading */ true);
}
return baseFamily;
}
}
}
#endif
return nullptr;
}
LocalFaceRec* FontList::FindLocalFace(const nsCString& aName) {
struct FaceNameComparator {
FaceNameComparator(FontList* aList, const nsCString& aTarget)
: mList(aList), mTarget(aTarget) {}
int operator()(const LocalFaceRec& aVal) const {
return Compare(mTarget,
nsDependentCString(aVal.mKey.BeginReading(mList)));
}
private:
FontList* mList;
const nsCString& mTarget;
};
Header& header = GetHeader();
LocalFaceRec* faces = LocalFaces();
size_t match;
if (faces && BinarySearchIf(faces, 0, header.mLocalFaceCount,
FaceNameComparator(this, aName), &match)) {
return &faces[match];
}
return nullptr;
}
void FontList::SearchForLocalFace(const nsACString& aName, Family** aFamily,
Face** aFace) {
Header& header = GetHeader();
MOZ_ASSERT(header.mLocalFaceCount == 0,
"do not use when local face names are already set up!");
LOG_FONTLIST(
("(shared-fontlist) local face search for (%s)", aName.BeginReading()));
char initial = aName[0];
Family* families = Families();
if (!families) {
return;
}
for (uint32_t i = 0; i < header.mFamilyCount; i++) {
Family* family = &families[i];
if (family->Key().BeginReading(this)[0] != initial) {
continue;
}
LOG_FONTLIST(("(shared-fontlist) checking family (%s)",
family->Key().AsString(this).BeginReading()));
if (!family->IsInitialized()) {
if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(family)) {
continue;
}
}
Pointer* faces = family->Faces(this);
if (!faces) {
continue;
}
for (uint32_t j = 0; j < family->NumFaces(); j++) {
auto* face = faces[j].ToPtr<Face>(this);
if (!face) {
continue;
}
nsAutoCString psname, fullname;
if (gfxPlatformFontList::PlatformFontList()->ReadFaceNames(
family, face, psname, fullname)) {
LOG_FONTLIST(("(shared-fontlist) read psname (%s) fullname (%s)",
psname.get(), fullname.get()));
ToLowerCase(psname);
ToLowerCase(fullname);
if (aName == psname || aName == fullname) {
*aFamily = family;
*aFace = face;
return;
}
}
}
}
}
size_t FontList::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
size_t FontList::SizeOfExcludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const {
size_t result = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (const auto& b : mBlocks) {
result += aMallocSizeOf(b.get()) + aMallocSizeOf(b->mShmem.get());
}
return result;
}
size_t FontList::AllocatedShmemSize() const {
size_t result = 0;
for (const auto& b : mBlocks) {
result += b->BlockSize();
}
return result;
}
} // namespace fontlist
} // namespace mozilla