diff options
Diffstat (limited to 'gfx/thebes/SharedFontList.cpp')
-rw-r--r-- | gfx/thebes/SharedFontList.cpp | 1361 |
1 files changed, 1361 insertions, 0 deletions
diff --git a/gfx/thebes/SharedFontList.cpp b/gfx/thebes/SharedFontList.cpp new file mode 100644 index 0000000000..e9f64089be --- /dev/null +++ b/gfx/thebes/SharedFontList.cpp @@ -0,0 +1,1361 @@ +/* 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, Pointer aFacePtr, + gfxCharacterMap* aCharMap) + : Runnable("SetCharMapRunnable"), + mListGeneration(aListGeneration), + mFacePtr(aFacePtr), + mCharMap(aCharMap) {} + + NS_IMETHOD Run() override { + auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + if (!list || list->GetGeneration() != mListGeneration) { + return NS_OK; + } + dom::ContentChild::GetSingleton()->SendSetCharacterMap(mListGeneration, + mFacePtr, *mCharMap); + return NS_OK; + } + + private: + uint32_t mListGeneration; + Pointer mFacePtr; + RefPtr<gfxCharacterMap> mCharMap; +}; + +void Face::SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap) { + if (!XRE_IsParentProcess()) { + Pointer ptr = aList->ToSharedPointer(this); + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendSetCharacterMap( + aList->GetGeneration(), ptr, *aCharMap); + } else { + NS_DispatchToMainThread( + new SetCharMapRunnable(aList->GetGeneration(), ptr, 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); + } + } + } + + 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 + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap( + aList->GetGeneration(), aList->ToSharedPointer(this)); + return; + } + NS_DispatchToMainThread(NS_NewRunnableFunction( + "SetupFamilyCharMap callback", + [gen = aList->GetGeneration(), ptr = aList->ToSharedPointer(this)] { + dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap(gen, ptr); + })); + 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; + } +} + +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(); + + // 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(); + 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 + // https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/02/24/90/36/WPF%20Font%20Selection%20Model.pdf + // 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; + } + } + } + } +} + +Pointer FontList::ToSharedPointer(const void* aPtr) { + const char* p = (const char*)aPtr; + const uint32_t blockCount = mBlocks.Length(); + for (uint32_t i = 0; i < blockCount; ++i) { + const char* blockAddr = (const char*)mBlocks[i]->Memory(); + if (p >= blockAddr && p < blockAddr + SHM_BLOCK_SIZE) { + return Pointer(i, p - blockAddr); + } + } + MOZ_DIAGNOSTIC_ASSERT(false, "invalid shared-memory pointer"); + return Pointer::Null(); +} + +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 |