diff --git a/layout/style/FontFace.cpp b/layout/style/FontFace.cpp index bf765d52e14..f6db634df56 100644 --- a/layout/style/FontFace.cpp +++ b/layout/style/FontFace.cpp @@ -15,7 +15,30 @@ using namespace mozilla::dom; -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FontFace, mParent, mLoaded, mRule) +NS_IMPL_CYCLE_COLLECTION_CLASS(FontFace) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FontFace) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoaded) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRule) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FontFace) + if (!tmp->IsInFontFaceSet()) { + tmp->mFontFaceSet->RemoveUnavailableFontFace(tmp); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoaded) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRule) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FontFace) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FontFace) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY @@ -29,11 +52,11 @@ FontFace::FontFace(nsISupports* aParent, nsPresContext* aPresContext) : mParent(aParent) , mPresContext(aPresContext) , mStatus(FontFaceLoadStatus::Unloaded) + , mFontFaceSet(aPresContext->Fonts()) + , mInFontFaceSet(false) { MOZ_COUNT_CTOR(FontFace); - MOZ_ASSERT(mPresContext); - SetIsDOMBinding(); nsCOMPtr global = do_QueryInterface(aParent); @@ -49,6 +72,10 @@ FontFace::~FontFace() MOZ_COUNT_DTOR(FontFace); SetUserFontEntry(nullptr); + + if (mFontFaceSet && !IsInFontFaceSet()) { + mFontFaceSet->RemoveUnavailableFontFace(this); + } } JSObject* @@ -84,6 +111,7 @@ FontFace::CreateForRule(nsISupports* aGlobal, nsRefPtr obj = new FontFace(aGlobal, aPresContext); obj->mRule = aRule; + obj->mInFontFaceSet = true; obj->SetUserFontEntry(aUserFontEntry); return obj.forget(); } @@ -116,6 +144,7 @@ FontFace::Constructor(const GlobalObject& aGlobal, } nsRefPtr obj = new FontFace(global, presContext); + obj->mFontFaceSet->AddUnavailableFontFace(obj); return obj.forget(); } @@ -433,6 +462,20 @@ FontFace::GetFamilyName(nsString& aResult) return !aResult.IsEmpty(); } +void +FontFace::DisconnectFromRule() +{ + MOZ_ASSERT(IsConnected()); + + // Make a copy of the descriptors. + mDescriptors = new CSSFontFaceDescriptors; + mRule->GetDescriptors(*mDescriptors); + + mRule->SetFontFace(nullptr); + mRule = nullptr; + mInFontFaceSet = false; +} + // -- FontFace::Entry -------------------------------------------------------- /* virtual */ void diff --git a/layout/style/FontFace.h b/layout/style/FontFace.h index 18918ac9a44..e135504a92f 100644 --- a/layout/style/FontFace.h +++ b/layout/style/FontFace.h @@ -19,6 +19,7 @@ namespace mozilla { struct CSSFontFaceDescriptors; namespace dom { struct FontFaceDescriptors; +class FontFaceSet; class Promise; class StringOrArrayBufferOrArrayBufferView; } @@ -78,6 +79,8 @@ public: gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; } void SetUserFontEntry(gfxUserFontEntry* aEntry); + bool IsInFontFaceSet() { return mInFontFaceSet; } + /** * Gets the family name of the FontFace as a raw string (such as 'Times', as * opposed to GetFamily, which returns a CSS-escaped string, such as @@ -85,6 +88,17 @@ public: */ bool GetFamilyName(nsString& aResult); + /** + * Returns whether this object is CSS-connected, i.e. reflecting an + * @font-face rule. + */ + bool IsConnected() const { return mRule; } + + /** + * Breaks the connection between this FontFace and its @font-face rule. + */ + void DisconnectFromRule(); + // Web IDL static already_AddRefed Constructor(const GlobalObject& aGlobal, @@ -162,6 +176,13 @@ private: // a CSS-connected FontFace object. For CSS-connected objects, we use // the descriptors stored in mRule. nsAutoPtr mDescriptors; + + // The FontFaceSet this FontFace is associated with, regardless of whether + // it is currently "in" the set. + nsRefPtr mFontFaceSet; + + // Whether this FontFace appears in the FontFaceSet. + bool mInFontFaceSet; }; } // namespace dom diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp index 88968b494e6..e90f2d4b93d 100644 --- a/layout/style/FontFaceSet.cpp +++ b/layout/style/FontFaceSet.cpp @@ -208,9 +208,14 @@ FontFaceSet::DestroyUserFontSet() mPresContext = nullptr; mLoaders.EnumerateEntries(DestroyIterator, nullptr); for (size_t i = 0; i < mConnectedFaces.Length(); i++) { + mConnectedFaces[i].mFontFace->DisconnectFromRule(); mConnectedFaces[i].mFontFace->SetUserFontEntry(nullptr); } + for (size_t i = 0; i < mUnavailableFaces.Length(); i++) { + mUnavailableFaces[i]->SetUserFontEntry(nullptr); + } mConnectedFaces.Clear(); + mUnavailableFaces.Clear(); mReady = nullptr; mUserFontSet = nullptr; } @@ -401,8 +406,8 @@ FontFaceSet::UpdateRules(const nsTArray& aRules) // it when the FontFace is GCed, if we can detect that. size_t count = oldRecords.Length(); for (size_t i = 0; i < count; ++i) { - gfxUserFontEntry* userFontEntry = - oldRecords[i].mFontFace->GetUserFontEntry(); + nsRefPtr f = oldRecords[i].mFontFace; + gfxUserFontEntry* userFontEntry = f->GetUserFontEntry(); if (userFontEntry) { nsFontFaceLoader* loader = userFontEntry->GetLoader(); if (loader) { @@ -410,6 +415,13 @@ FontFaceSet::UpdateRules(const nsTArray& aRules) RemoveLoader(loader); } } + + // Any left over FontFace objects should also cease being CSS-connected. + MOZ_ASSERT(!mUnavailableFaces.Contains(f), + "FontFace should not occur in mUnavailableFaces twice"); + + mUnavailableFaces.AppendElement(f); + f->DisconnectFromRule(); } } @@ -455,6 +467,9 @@ FontFaceSet::InsertConnectedFontFace( return; } + bool remove = false; + size_t removeIndex; + // This is a CSS-connected FontFace. First, we check in aOldRecords; if // the FontFace for the rule exists there, just move it to the new record // list, and put the entry into the appropriate family. @@ -471,6 +486,10 @@ FontFaceSet::InsertConnectedFontFace( aFontFace->GetDesc(eCSSFontDesc_Src, val); nsCSSUnit unit = val.GetUnit(); if (unit == eCSSUnit_Array && HasLocalSrc(val.GetArrayValue())) { + // Remove the old record, but wait to see if we successfully create a + // new user font entry below. + remove = true; + removeIndex = i; break; } } @@ -502,6 +521,16 @@ FontFaceSet::InsertConnectedFontFace( return; } + if (remove) { + // Although we broke out of the aOldRecords loop above, since we found + // src local usage, and we're not using the old user font entry, we still + // are adding a record to mConnectedFaces with the same FontFace object. + // Remove the old record so that we don't have the same FontFace listed + // in both mConnectedFaces and oldRecords, which would cause us to call + // DisconnectFromRule on a FontFace that should still be connected. + aOldRecords.RemoveElementAt(removeIndex); + } + FontFaceRecord rec; rec.mFontFace = aFontFace; rec.mSheetType = aSheetType; @@ -1017,6 +1046,29 @@ FontFaceSet::FontFaceForRule(nsCSSFontFaceRule* aRule) return newFontFace; } +void +FontFaceSet::AddUnavailableFontFace(FontFace* aFontFace) +{ + MOZ_ASSERT(!aFontFace->IsInFontFaceSet()); + MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace)); + + mUnavailableFaces.AppendElement(aFontFace); +} + +void +FontFaceSet::RemoveUnavailableFontFace(FontFace* aFontFace) +{ + MOZ_ASSERT(!aFontFace->IsConnected()); + MOZ_ASSERT(!aFontFace->IsInFontFaceSet()); + + // We might not actually find the FontFace in mUnavailableFaces, since we + // might be shutting down the document and had DestroyUserFontSet called + // on us, which clears out mUnavailableFaces. + mUnavailableFaces.RemoveElement(aFontFace); + + MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace)); +} + // -- FontFaceSet::UserFontSet ------------------------------------------------ /* virtual */ nsresult diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h index 3ff63ab523e..e6e6a5c73e5 100644 --- a/layout/style/FontFaceSet.h +++ b/layout/style/FontFaceSet.h @@ -112,6 +112,18 @@ public: void IncrementGeneration(bool aIsRebuild = false); + /** + * Adds the specified FontFace to the mUnavailableFaces array. This is called + * when a new FontFace object has just been created in JS by the author. + */ + void AddUnavailableFontFace(FontFace* aFontFace); + + /** + * Removes the specified FontFace from the mUnavailableFaces array. This + * is called when a FontFace object is about be destroyed. + */ + void RemoveUnavailableFontFace(FontFace* aFontFace); + // -- Web IDL -------------------------------------------------------------- IMPL_EVENT_HANDLER(loading) @@ -193,6 +205,10 @@ private: // The CSS-connected FontFace objects in the FontFaceSet. nsTArray mConnectedFaces; + + // The unconnected FontFace objects that have not been added to + // this FontFaceSet. + nsTArray mUnavailableFaces; }; } // namespace dom diff --git a/layout/style/nsCSSRules.h b/layout/style/nsCSSRules.h index 7957218f51f..31803d68bc9 100644 --- a/layout/style/nsCSSRules.h +++ b/layout/style/nsCSSRules.h @@ -280,6 +280,9 @@ public: mozilla::dom::FontFace* GetFontFace() const { return mFontFace; } void SetFontFace(mozilla::dom::FontFace* aFontFace) { mFontFace = aFontFace; } + void GetDescriptors(mozilla::CSSFontFaceDescriptors& aDescriptors) const + { aDescriptors = mDecl.mDescriptors; } + protected: ~nsCSSFontFaceRule() {}