diff options
Diffstat (limited to 'layout/svg/SVGObserverUtils.cpp')
-rw-r--r-- | layout/svg/SVGObserverUtils.cpp | 1876 |
1 files changed, 1876 insertions, 0 deletions
diff --git a/layout/svg/SVGObserverUtils.cpp b/layout/svg/SVGObserverUtils.cpp new file mode 100644 index 0000000000..551e7c67b7 --- /dev/null +++ b/layout/svg/SVGObserverUtils.cpp @@ -0,0 +1,1876 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Main header first: +#include "SVGObserverUtils.h" + +// Keep others in (case-insensitive) order: +#include "mozilla/css/ImageLoader.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGMPathElement.h" +#include "mozilla/dom/SVGTextPathElement.h" +#include "mozilla/dom/SVGUseElement.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" +#include "nsCSSFrameConstructor.h" +#include "nsCycleCollectionParticipant.h" +#include "nsHashKeys.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsInterfaceHashtable.h" +#include "nsIReflowCallback.h" +#include "nsISupportsImpl.h" +#include "nsLayoutUtils.h" +#include "nsNetUtil.h" +#include "nsTHashtable.h" +#include "nsURIHashKey.h" +#include "SVGFilterFrame.h" +#include "SVGMarkerFrame.h" +#include "SVGPaintServerFrame.h" + +using namespace mozilla::dom; + +namespace mozilla { + +bool URLAndReferrerInfo::operator==(const URLAndReferrerInfo& aRHS) const { + bool uriEqual = false, referrerEqual = false; + this->mURI->Equals(aRHS.mURI, &uriEqual); + this->mReferrerInfo->Equals(aRHS.mReferrerInfo, &referrerEqual); + + return uriEqual && referrerEqual; +} + +class URLAndReferrerInfoHashKey : public PLDHashEntryHdr { + public: + using KeyType = const URLAndReferrerInfo*; + using KeyTypePointer = const URLAndReferrerInfo*; + + explicit URLAndReferrerInfoHashKey(const URLAndReferrerInfo* aKey) noexcept + : mKey(aKey) { + MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey); + } + URLAndReferrerInfoHashKey(URLAndReferrerInfoHashKey&& aToMove) noexcept + : PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) { + MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey); + } + MOZ_COUNTED_DTOR(URLAndReferrerInfoHashKey) + + const URLAndReferrerInfo* GetKey() const { return mKey; } + + bool KeyEquals(const URLAndReferrerInfo* aKey) const { + if (!mKey) { + return !aKey; + } + return *mKey == *aKey; + } + + static const URLAndReferrerInfo* KeyToPointer( + const URLAndReferrerInfo* aKey) { + return aKey; + } + + static PLDHashNumber HashKey(const URLAndReferrerInfo* aKey) { + if (!aKey) { + // If the key is null, return hash for empty string. + return HashString(""_ns); + } + nsAutoCString urlSpec, referrerSpec; + // nsURIHashKey ignores GetSpec() failures, so we do too: + Unused << aKey->GetURI()->GetSpec(urlSpec); + return AddToHash( + HashString(urlSpec), + static_cast<ReferrerInfo*>(aKey->GetReferrerInfo())->Hash()); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + RefPtr<const URLAndReferrerInfo> mKey; +}; + +/** + * Return a baseURL for resolving a local-ref URL. + * + * @param aContent an element which uses a local-ref property. Here are some + * examples: + * <rect fill=url(#foo)> + * <circle clip-path=url(#foo)> + * <use xlink:href="#foo"> + */ +static already_AddRefed<nsIURI> GetBaseURLForLocalRef(nsIContent* content, + nsIURI* aURI) { + MOZ_ASSERT(content); + + // Content is in a shadow tree. If this URL was specified in the subtree + // referenced by the <use>, element, and that subtree came from a separate + // resource document, then we want the fragment-only URL to resolve to an + // element from the resource document. Otherwise, the URL was specified + // somewhere in the document with the <use> element, and we want the + // fragment-only URL to resolve to an element in that document. + if (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) { + if (nsIURI* originalURI = use->GetSourceDocURI()) { + bool isEqualsExceptRef = false; + aURI->EqualsExceptRef(originalURI, &isEqualsExceptRef); + if (isEqualsExceptRef) { + return do_AddRef(originalURI); + } + } + } + + // For a local-reference URL, resolve that fragment against the current + // document that relative URLs are resolved against. + return do_AddRef(content->OwnerDoc()->GetDocumentURI()); +} + +static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef( + nsIFrame* aFrame, const StyleComputedImageUrl& aURL) { + MOZ_ASSERT(aFrame); + + nsCOMPtr<nsIURI> uri = aURL.GetURI(); + + if (aURL.IsLocalRef()) { + uri = GetBaseURLForLocalRef(aFrame->GetContent(), uri); + uri = aURL.ResolveLocalRef(uri); + } + + if (!uri) { + return nullptr; + } + + return do_AddRef(new URLAndReferrerInfo(uri, aURL.ExtraData())); +} + +static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef( + nsIContent* aContent, const nsAString& aURL) { + // Like GetBaseURLForLocalRef, we want to resolve the + // URL against any <use> element shadow tree's source document. + // + // Unlike GetBaseURLForLocalRef, we are assuming that the URL was specified + // directly on mFrame's content (because this ResolveURLUsingLocalRef + // overload is used for href="" attributes and not CSS URL values), so there + // is no need to check whether the URL was specified / inherited from + // outside the shadow tree. + nsIURI* base = nullptr; + const Encoding* encoding = nullptr; + if (SVGUseElement* use = aContent->GetContainingSVGUseShadowHost()) { + base = use->GetSourceDocURI(); + encoding = use->GetSourceDocCharacterSet(); + } + + if (!base) { + base = aContent->OwnerDoc()->GetDocumentURI(); + encoding = aContent->OwnerDoc()->GetDocumentCharacterSet(); + } + + nsCOMPtr<nsIURI> uri; + Unused << NS_NewURI(getter_AddRefs(uri), aURL, WrapNotNull(encoding), base); + + if (!uri) { + return nullptr; + } + + // There's no clear refererer policy spec about non-CSS SVG resource + // references Bug 1415044 to investigate which referrer we should use + nsIReferrerInfo* referrerInfo = + aContent->OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources(); + + return do_AddRef(new URLAndReferrerInfo(uri, referrerInfo)); +} + +class SVGFilterObserverList; + +/** + * A class used as a member of the "observer" classes below to help them + * avoid dereferencing their frame during presshell teardown when their frame + * may have been destroyed (leaving their pointer to their frame dangling). + * + * When a presshell is torn down, the properties for each frame may not be + * deleted until after the frames are destroyed. "Observer" objects (attached + * as frame properties) must therefore check whether the presshell is being + * torn down before using their pointer to their frame. + * + * mFramePresShell may be null, but when mFrame is non-null, mFramePresShell + * is guaranteed to be non-null, too. + */ +struct SVGFrameReferenceFromProperty { + explicit SVGFrameReferenceFromProperty(nsIFrame* aFrame) + : mFrame(aFrame), mFramePresShell(aFrame->PresShell()) {} + + // Clear our reference to the frame. + void Detach() { + mFrame = nullptr; + mFramePresShell = nullptr; + } + + // null if the frame has become invalid + nsIFrame* Get() { + if (mFramePresShell && mFramePresShell->IsDestroying()) { + Detach(); // mFrame is no longer valid. + } + return mFrame; + } + + private: + // The frame that our property is attached to (may be null). + nsIFrame* mFrame; + PresShell* mFramePresShell; +}; + +void SVGRenderingObserver::StartObserving() { + Element* target = GetReferencedElementWithoutObserving(); + if (target) { + target->AddMutationObserver(this); + } +} + +void SVGRenderingObserver::StopObserving() { + Element* target = GetReferencedElementWithoutObserving(); + + if (target) { + target->RemoveMutationObserver(this); + if (mInObserverSet) { + SVGObserverUtils::RemoveRenderingObserver(target, this); + mInObserverSet = false; + } + } + NS_ASSERTION(!mInObserverSet, "still in an observer set?"); +} + +Element* SVGRenderingObserver::GetAndObserveReferencedElement() { +#ifdef DEBUG + DebugObserverSet(); +#endif + Element* referencedElement = GetReferencedElementWithoutObserving(); + if (referencedElement && !mInObserverSet) { + SVGObserverUtils::AddRenderingObserver(referencedElement, this); + mInObserverSet = true; + } + return referencedElement; +} + +nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame() { + Element* referencedElement = GetAndObserveReferencedElement(); + return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr; +} + +nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame( + LayoutFrameType aFrameType, bool* aOK) { + nsIFrame* frame = GetAndObserveReferencedFrame(); + if (frame) { + if (frame->Type() == aFrameType) { + return frame; + } + if (aOK) { + *aOK = false; + } + } + return nullptr; +} + +void SVGRenderingObserver::OnNonDOMMutationRenderingChange() { + OnRenderingChange(); +} + +void SVGRenderingObserver::NotifyEvictedFromRenderingObserverSet() { + mInObserverSet = false; // We've been removed from rendering-obs. set. + StopObserving(); // Stop observing mutations too. +} + +void SVGRenderingObserver::AttributeChanged(dom::Element* aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) { + if (aElement->IsInNativeAnonymousSubtree()) { + // Don't observe attribute changes in native-anonymous subtrees like + // scrollbars. + return; + } + + // An attribute belonging to the element that we are observing *or one of its + // descendants* has changed. + // + // In the case of observing a gradient element, say, we want to know if any + // of its 'stop' element children change, but we don't actually want to do + // anything for changes to SMIL element children, for example. Maybe it's not + // worth having logic to optimize for that, but in most cases it could be a + // small check? + // + // XXXjwatt: do we really want to blindly break the link between our + // observers and ourselves for all attribute changes? For non-ID changes + // surely that is unnecessary. + + OnRenderingChange(); +} + +void SVGRenderingObserver::ContentAppended(nsIContent* aFirstNewContent) { + OnRenderingChange(); +} + +void SVGRenderingObserver::ContentInserted(nsIContent* aChild) { + OnRenderingChange(); +} + +void SVGRenderingObserver::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + OnRenderingChange(); +} + +/** + * SVG elements reference supporting resources by element ID. We need to + * track when those resources change and when the document changes in ways + * that affect which element is referenced by a given ID (e.g., when + * element IDs change). The code here is responsible for that. + * + * When a frame references a supporting resource, we create a property + * object derived from SVGIDRenderingObserver to manage the relationship. The + * property object is attached to the referencing frame. + */ +class SVGIDRenderingObserver : public SVGRenderingObserver { + public: + // Callback for checking if the element being observed is valid for this + // observer. Note that this may be called during construction, before the + // deriving class is fully constructed. + using TargetIsValidCallback = bool (*)(const Element&); + SVGIDRenderingObserver( + URLAndReferrerInfo* aURI, nsIContent* aObservingContent, + bool aReferenceImage, + uint32_t aCallbacks = kAttributeChanged | kContentAppended | + kContentInserted | kContentRemoved, + TargetIsValidCallback aTargetIsValidCallback = nullptr); + + void Traverse(nsCycleCollectionTraversalCallback* aCB); + + protected: + virtual ~SVGIDRenderingObserver() { + // This needs to call our GetReferencedElementWithoutObserving override, + // so must be called here rather than in our base class's dtor. + StopObserving(); + } + + void TargetChanged() { + mTargetIsValid = ([this] { + Element* observed = mObservedElementTracker.get(); + if (!observed) { + return false; + } + // If the content is observing an ancestor, then return the target is not + // valid. + // + // TODO(emilio): Should we allow content observing its own descendants? + // That seems potentially-bad as well. + if (observed->OwnerDoc() == mObservingContent->OwnerDoc() && + nsContentUtils::ContentIsHostIncludingDescendantOf(mObservingContent, + observed)) { + return false; + } + if (mTargetIsValidCallback) { + return mTargetIsValidCallback(*observed); + } + return true; + }()); + } + + Element* GetReferencedElementWithoutObserving() final { + return mTargetIsValid ? mObservedElementTracker.get() : nullptr; + } + + void OnRenderingChange() override; + + /** + * Helper that provides a reference to the element with the ID that our + * observer wants to observe, and that will invalidate our observer if the + * element that that ID identifies changes to a different element (or none). + */ + class ElementTracker final : public IDTracker { + public: + explicit ElementTracker(SVGIDRenderingObserver* aOwningObserver) + : mOwningObserver(aOwningObserver) {} + + protected: + void ElementChanged(Element* aFrom, Element* aTo) override { + // Call OnRenderingChange() before the target changes, so that + // mIsTargetValid reflects the right state. + mOwningObserver->OnRenderingChange(); + mOwningObserver->StopObserving(); + IDTracker::ElementChanged(aFrom, aTo); + mOwningObserver->TargetChanged(); + mOwningObserver->StartObserving(); + // And same after the target changes, for the same reason. + mOwningObserver->OnRenderingChange(); + } + /** + * Override IsPersistent because we want to keep tracking the element + * for the ID even when it changes. + */ + bool IsPersistent() override { return true; } + + private: + SVGIDRenderingObserver* mOwningObserver; + }; + + ElementTracker mObservedElementTracker; + RefPtr<Element> mObservingContent; + bool mTargetIsValid = false; + TargetIsValidCallback mTargetIsValidCallback; +}; + +/** + * Note that in the current setup there are two separate observer lists. + * + * In SVGIDRenderingObserver's ctor, the new object adds itself to the + * mutation observer list maintained by the referenced element. In this way the + * SVGIDRenderingObserver is notified if there are any attribute or content + * tree changes to the element or any of its *descendants*. + * + * In SVGIDRenderingObserver::GetAndObserveReferencedElement() the + * SVGIDRenderingObserver object also adds itself to an + * SVGRenderingObserverSet object belonging to the referenced + * element. + * + * XXX: it would be nice to have a clear and concise executive summary of the + * benefits/necessity of maintaining a second observer list. + */ +SVGIDRenderingObserver::SVGIDRenderingObserver( + URLAndReferrerInfo* aURI, nsIContent* aObservingContent, + bool aReferenceImage, uint32_t aCallbacks, + TargetIsValidCallback aTargetIsValidCallback) + : SVGRenderingObserver(aCallbacks), + mObservedElementTracker(this), + mObservingContent(aObservingContent->AsElement()), + mTargetIsValidCallback(aTargetIsValidCallback) { + // Start watching the target element + nsIURI* uri = nullptr; + nsIReferrerInfo* referrerInfo = nullptr; + if (aURI) { + uri = aURI->GetURI(); + referrerInfo = aURI->GetReferrerInfo(); + } + + mObservedElementTracker.ResetToURIFragmentID( + aObservingContent, uri, referrerInfo, true, aReferenceImage); + TargetChanged(); + StartObserving(); +} + +void SVGIDRenderingObserver::Traverse(nsCycleCollectionTraversalCallback* aCB) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mObservingContent"); + aCB->NoteXPCOMChild(mObservingContent); + mObservedElementTracker.Traverse(aCB); +} + +void SVGIDRenderingObserver::OnRenderingChange() { + if (mObservedElementTracker.get() && mInObserverSet) { + SVGObserverUtils::RemoveRenderingObserver(mObservedElementTracker.get(), + this); + mInObserverSet = false; + } +} + +class SVGRenderingObserverProperty : public SVGIDRenderingObserver { + public: + NS_DECL_ISUPPORTS + + SVGRenderingObserverProperty( + URLAndReferrerInfo* aURI, nsIFrame* aFrame, bool aReferenceImage, + uint32_t aCallbacks = kAttributeChanged | kContentAppended | + kContentInserted | kContentRemoved, + TargetIsValidCallback aTargetIsValidCallback = nullptr) + : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage, + aCallbacks, aTargetIsValidCallback), + mFrameReference(aFrame) {} + + protected: + virtual ~SVGRenderingObserverProperty() = default; // non-public + + void OnRenderingChange() override; + + SVGFrameReferenceFromProperty mFrameReference; +}; + +NS_IMPL_ISUPPORTS(SVGRenderingObserverProperty, nsIMutationObserver) + +void SVGRenderingObserverProperty::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (!mTargetIsValid) { + return; + } + + nsIFrame* frame = mFrameReference.Get(); + + if (frame && frame->HasAllStateBits(NS_FRAME_SVG_LAYOUT)) { + // We need to notify anything that is observing the referencing frame or + // any of its ancestors that the referencing frame has been invalidated. + // Since walking the parent chain checking for observers is expensive we + // do that using a change hint (multiple change hints of the same type are + // coalesced). + nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(), + RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + } +} + +static bool IsSVGGeometryElement(const Element& aObserved) { + return aObserved.IsSVGGeometryElement(); +} + +class SVGTextPathObserver final : public SVGRenderingObserverProperty { + public: + SVGTextPathObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage, + kAttributeChanged, IsSVGGeometryElement) {} + + protected: + void OnRenderingChange() override; +}; + +void SVGTextPathObserver::OnRenderingChange() { + SVGRenderingObserverProperty::OnRenderingChange(); + + if (!mTargetIsValid) { + return; + } + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) { + return; + } + + MOZ_ASSERT(frame->IsSVGFrame() || frame->IsInSVGTextSubtree(), + "SVG frame expected"); + + MOZ_ASSERT(frame->GetContent()->IsSVGElement(nsGkAtoms::textPath), + "expected frame for a <textPath> element"); + + auto* text = static_cast<SVGTextFrame*>( + nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText)); + MOZ_ASSERT(text, "expected to find an ancestor SVGTextFrame"); + if (text) { + text->AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); + + if (SVGUtils::AnyOuterSVGIsCallingReflowSVG(text)) { + text->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); + if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + text->ReflowSVGNonDisplayText(); + } else { + text->ReflowSVG(); + } + } else { + text->ScheduleReflowSVG(); + } + } +} + +class SVGMPathObserver final : public SVGIDRenderingObserver { + public: + NS_DECL_ISUPPORTS + + SVGMPathObserver(URLAndReferrerInfo* aURI, SVGMPathElement* aElement) + : SVGIDRenderingObserver(aURI, aElement, /* aReferenceImage = */ false, + kAttributeChanged, IsSVGGeometryElement) {} + + protected: + virtual ~SVGMPathObserver() = default; // non-public + + void OnRenderingChange() override; +}; + +NS_IMPL_ISUPPORTS(SVGMPathObserver, nsIMutationObserver) + +void SVGMPathObserver::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (!mTargetIsValid) { + return; + } + + auto* element = static_cast<SVGMPathElement*>(mObservingContent.get()); + element->NotifyParentOfMpathChange(); +} + +class SVGMarkerObserver final : public SVGRenderingObserverProperty { + public: + SVGMarkerObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage, + kAttributeChanged | kContentAppended | + kContentInserted | kContentRemoved) {} + + protected: + void OnRenderingChange() override; +}; + +void SVGMarkerObserver::OnRenderingChange() { + SVGRenderingObserverProperty::OnRenderingChange(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) { + return; + } + + MOZ_ASSERT(frame->IsSVGFrame(), "SVG frame expected"); + + // Don't need to request ReflowFrame if we're being reflowed. + // Because mRect for SVG frames includes the bounds of any markers + // (see the comment for nsIFrame::GetRect), the referencing frame must be + // reflowed for any marker changes. + if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { + // XXXjwatt: We need to unify SVG into standard reflow so we can just use + // nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here. + // XXXSDL KILL THIS!!! + SVGUtils::ScheduleReflowSVG(frame); + } + frame->PresContext()->RestyleManager()->PostRestyleEvent( + frame->GetContent()->AsElement(), RestyleHint{0}, + nsChangeHint_RepaintFrame); +} + +class SVGPaintingProperty : public SVGRenderingObserverProperty { + public: + SVGPaintingProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} + + protected: + void OnRenderingChange() override; +}; + +void SVGPaintingProperty::OnRenderingChange() { + SVGRenderingObserverProperty::OnRenderingChange(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) { + return; + } + + if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + frame->InvalidateFrameSubtree(); + } else { + for (nsIFrame* f = frame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + f->InvalidateFrame(); + } + } +} + +// Observer for -moz-element(#element). Note that the observed element does not +// have to be an SVG element. +class SVGMozElementObserver final : public SVGPaintingProperty { + public: + SVGMozElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame) + : SVGPaintingProperty(aURI, aFrame, /* aReferenceImage = */ true) {} + + // We only return true here because GetAndObserveBackgroundImage uses us + // to implement observing of arbitrary elements (including HTML elements) + // that may require us to repaint if the referenced element is reflowed. + // Bug 1496065 has been filed to remove that support though. + bool ObservesReflow() override { return true; } +}; + +/** + * For content with `background-clip: text`. + * + * This observer is unusual in that the observing frame and observed frame are + * the same frame. This is because the observing frame is observing for reflow + * of its descendant text nodes, since such reflows may not result in the + * frame's nsDisplayBackground changing. In other words, Display List Based + * Invalidation may not invalidate the frame's background, so we need this + * observer to make sure that happens. + * + * XXX: It's questionable whether we should even be [ab]using the SVG observer + * mechanism for `background-clip:text`. Since we know that the observed frame + * is the frame we need to invalidate, we could just check the computed style + * in the (one) place where we pass INVALIDATE_REFLOW and invalidate there... + */ +class BackgroundClipRenderingObserver : public SVGRenderingObserver { + public: + explicit BackgroundClipRenderingObserver(nsIFrame* aFrame) : mFrame(aFrame) {} + + NS_DECL_ISUPPORTS + + private: + // We do not call StopObserving() since the observing and observed element + // are the same element (and because we could crash - see bug 1556441). + virtual ~BackgroundClipRenderingObserver() = default; + + Element* GetReferencedElementWithoutObserving() final { + return mFrame->GetContent()->AsElement(); + } + + void OnRenderingChange() final; + + /** + * Observing for mutations is not enough. A new font loading and applying + * to the text content could cause it to reflow, and we need to invalidate + * for that. + */ + bool ObservesReflow() final { return true; } + + // The observer and observee! + nsIFrame* mFrame; +}; + +NS_IMPL_ISUPPORTS(BackgroundClipRenderingObserver, nsIMutationObserver) + +void BackgroundClipRenderingObserver::OnRenderingChange() { + for (nsIFrame* f = mFrame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + f->InvalidateFrame(); + } +} + +static bool IsSVGFilterElement(const Element& aObserved) { + return aObserved.IsSVGElement(nsGkAtoms::filter); +} + +/** + * In a filter chain, there can be multiple SVG reference filters. + * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); + * + * This class keeps track of one SVG reference filter in a filter chain. + * e.g. url(#svg-filter-1) + * + * It fires invalidations when the SVG filter element's id changes or when + * the SVG filter element's content changes. + * + * The SVGFilterObserverList class manages a list of SVGFilterObservers. + */ +class SVGFilterObserver final : public SVGIDRenderingObserver { + public: + SVGFilterObserver(URLAndReferrerInfo* aURI, nsIContent* aObservingContent, + SVGFilterObserverList* aFilterChainObserver) + : SVGIDRenderingObserver(aURI, aObservingContent, false, + kAttributeChanged | kContentAppended | + kContentInserted | kContentRemoved, + IsSVGFilterElement), + mFilterObserverList(aFilterChainObserver) {} + + void DetachFromChainObserver() { mFilterObserverList = nullptr; } + + /** + * @return the filter frame, or null if there is no filter frame + */ + SVGFilterFrame* GetAndObserveFilterFrame(); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserver) + + // SVGIDRenderingObserver + void OnRenderingChange() override; + + protected: + virtual ~SVGFilterObserver() = default; // non-public + + SVGFilterObserverList* mFilterObserverList; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserver) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserver) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservedElementTracker) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservingContent) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserver) + tmp->StopObserving(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservedElementTracker); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservingContent) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +SVGFilterFrame* SVGFilterObserver::GetAndObserveFilterFrame() { + return static_cast<SVGFilterFrame*>( + GetAndObserveReferencedFrame(LayoutFrameType::SVGFilter, nullptr)); +} + +/** + * This class manages a list of SVGFilterObservers, which correspond to + * reference to SVG filters in a list of filters in a given 'filter' property. + * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); + * + * In the above example, the SVGFilterObserverList will manage two + * SVGFilterObservers, one for each of the references to SVG filters. CSS + * filters like "blur(10px)" don't reference filter elements, so they don't + * need an SVGFilterObserver. The style system invalidates changes to CSS + * filters. + * + * FIXME(emilio): Why do we need this as opposed to the individual observers we + * create in the constructor? + */ +class SVGFilterObserverList : public nsISupports { + public: + SVGFilterObserverList(Span<const StyleFilter> aFilters, + nsIContent* aFilteredElement, + nsIFrame* aFilteredFrame = nullptr); + + const nsTArray<RefPtr<SVGFilterObserver>>& GetObservers() const { + return mObservers; + } + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList) + + virtual void OnRenderingChange() = 0; + + protected: + virtual ~SVGFilterObserverList(); + + void DetachObservers() { + for (auto& observer : mObservers) { + observer->DetachFromChainObserver(); + } + } + + nsTArray<RefPtr<SVGFilterObserver>> mObservers; +}; + +void SVGFilterObserver::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (mFilterObserverList) { + mFilterObserverList->OnRenderingChange(); + } + + if (!mTargetIsValid) { + return; + } + + nsIFrame* frame = mObservingContent->GetPrimaryFrame(); + if (!frame) { + return; + } + + // Repaint asynchronously in case the filter frame is being torn down + nsChangeHint changeHint = nsChangeHint(nsChangeHint_RepaintFrame); + + // Since we don't call SVGRenderingObserverProperty:: + // OnRenderingChange, we have to add this bit ourselves. + if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // Changes should propagate out to things that might be observing + // the referencing frame or its ancestors. + changeHint |= nsChangeHint_InvalidateRenderingObservers; + } + + // Don't need to request UpdateOverflow if we're being reflowed. + if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { + changeHint |= nsChangeHint_UpdateOverflow; + } + frame->PresContext()->RestyleManager()->PostRestyleEvent( + mObservingContent, RestyleHint{0}, changeHint); +} + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserverList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserverList) + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserverList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserverList) + tmp->DetachObservers(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserverList) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +SVGFilterObserverList::SVGFilterObserverList(Span<const StyleFilter> aFilters, + nsIContent* aFilteredElement, + nsIFrame* aFilteredFrame) { + for (const auto& filter : aFilters) { + if (!filter.IsUrl()) { + continue; + } + + const auto& url = filter.AsUrl(); + + // aFilteredFrame can be null if this filter belongs to a + // CanvasRenderingContext2D. + RefPtr<URLAndReferrerInfo> filterURL; + if (aFilteredFrame) { + filterURL = ResolveURLUsingLocalRef(aFilteredFrame, url); + } else { + nsCOMPtr<nsIURI> resolvedURI = url.ResolveLocalRef(aFilteredElement); + if (resolvedURI) { + filterURL = new URLAndReferrerInfo(resolvedURI, url.ExtraData()); + } + } + + RefPtr<SVGFilterObserver> observer = + new SVGFilterObserver(filterURL, aFilteredElement, this); + mObservers.AppendElement(observer); + } +} + +SVGFilterObserverList::~SVGFilterObserverList() { DetachObservers(); } + +class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList { + public: + SVGFilterObserverListForCSSProp(Span<const StyleFilter> aFilters, + nsIFrame* aFilteredFrame) + : SVGFilterObserverList(aFilters, aFilteredFrame->GetContent(), + aFilteredFrame) {} + + protected: + void OnRenderingChange() override; + bool mInvalidating = false; +}; + +void SVGFilterObserverListForCSSProp::OnRenderingChange() { + if (mInvalidating) { + return; + } + AutoRestore<bool> guard(mInvalidating); + mInvalidating = true; + for (auto& observer : mObservers) { + observer->OnRenderingChange(); + } +} + +class SVGFilterObserverListForCanvasContext final + : public SVGFilterObserverList { + public: + SVGFilterObserverListForCanvasContext(CanvasRenderingContext2D* aContext, + Element* aCanvasElement, + Span<const StyleFilter> aFilters) + : SVGFilterObserverList(aFilters, aCanvasElement), mContext(aContext) {} + + void OnRenderingChange() override; + void DetachFromContext() { mContext = nullptr; } + + private: + CanvasRenderingContext2D* mContext; +}; + +void SVGFilterObserverListForCanvasContext::OnRenderingChange() { + if (!mContext) { + NS_WARNING( + "GFX: This should never be called without a context, except during " + "cycle collection (when DetachFromContext has been called)"); + return; + } + // Refresh the cached FilterDescription in mContext->CurrentState().filter. + // If this filter is not at the top of the state stack, we'll refresh the + // wrong filter, but that's ok, because we'll refresh the right filter + // when we pop the state stack in CanvasRenderingContext2D::Restore(). + // + // We don't need to flush, we're called by layout. + RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext); + kungFuDeathGrip->UpdateFilter(/* aFlushIfNeeded = */ false); +} + +class SVGMaskObserverList final : public nsISupports { + public: + explicit SVGMaskObserverList(nsIFrame* aFrame); + + // nsISupports + NS_DECL_ISUPPORTS + + const nsTArray<RefPtr<SVGPaintingProperty>>& GetObservers() const { + return mProperties; + } + + void ResolveImage(uint32_t aIndex); + + private: + virtual ~SVGMaskObserverList() = default; // non-public + nsTArray<RefPtr<SVGPaintingProperty>> mProperties; + nsIFrame* mFrame; +}; + +NS_IMPL_ISUPPORTS(SVGMaskObserverList, nsISupports) + +SVGMaskObserverList::SVGMaskObserverList(nsIFrame* aFrame) : mFrame(aFrame) { + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + + for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) { + const StyleComputedImageUrl* data = + svgReset->mMask.mLayers[i].mImage.GetImageRequestURLValue(); + RefPtr<URLAndReferrerInfo> maskUri; + if (data) { + maskUri = ResolveURLUsingLocalRef(aFrame, *data); + } + + bool hasRef = false; + if (maskUri) { + maskUri->GetURI()->GetHasRef(&hasRef); + } + + // Accrording to maskUri, SVGPaintingProperty's ctor may trigger an + // external SVG resource download, so we should pass maskUri in only if + // maskUri has a chance pointing to an SVG mask resource. + // + // And, an URL may refer to an SVG mask resource if it consists of + // a fragment. + SVGPaintingProperty* prop = new SVGPaintingProperty( + hasRef ? maskUri.get() : nullptr, aFrame, false); + mProperties.AppendElement(prop); + } +} + +void SVGMaskObserverList::ResolveImage(uint32_t aIndex) { + const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset(); + MOZ_ASSERT(aIndex < svgReset->mMask.mImageCount); + + const auto& image = svgReset->mMask.mLayers[aIndex].mImage; + if (image.IsResolved()) { + return; + } + MOZ_ASSERT(image.IsImageRequestType()); + Document* doc = mFrame->PresContext()->Document(); + const_cast<StyleImage&>(image).ResolveImage(*doc, nullptr); + if (imgRequestProxy* req = image.GetImageRequest()) { + // FIXME(emilio): What disassociates this request? + doc->StyleImageLoader()->AssociateRequestToFrame(req, mFrame); + } +} + +/** + * Used for gradient-to-gradient, pattern-to-pattern and filter-to-filter + * references to "template" elements (specified via the 'href' attributes). + */ +class SVGTemplateElementObserver : public SVGIDRenderingObserver { + public: + NS_DECL_ISUPPORTS + + SVGTemplateElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage, + kAttributeChanged | kContentAppended | + kContentInserted | kContentRemoved), + mFrameReference(aFrame) {} + + protected: + virtual ~SVGTemplateElementObserver() = default; // non-public + + void OnRenderingChange() override; + + SVGFrameReferenceFromProperty mFrameReference; +}; + +NS_IMPL_ISUPPORTS(SVGTemplateElementObserver, nsIMutationObserver) + +void SVGTemplateElementObserver::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (nsIFrame* frame = mFrameReference.Get()) { + SVGObserverUtils::InvalidateRenderingObservers(frame); + } +} + +/** + * An instance of this class is stored on an observed frame (as a frame + * property) whenever the frame has active rendering observers. It is used to + * store pointers to the SVGRenderingObserver instances belonging to any + * observing frames, allowing invalidations from the observed frame to be sent + * to all observing frames. + * + * SVGRenderingObserver instances that are added are not strongly referenced, + * so they must remove themselves before they die. + * + * This class is "single-shot", which is to say that when something about the + * observed element changes, InvalidateAll() clears our hashtable of + * SVGRenderingObservers. SVGRenderingObserver objects will be added back + * again if/when the observing frame looks up our observed frame to use it. + * + * XXXjwatt: is this the best thing to do nowadays? Back when that mechanism + * landed in bug 330498 we had two pass, recursive invalidation up the frame + * tree, and I think reference loops were a problem. Nowadays maybe a flag + * on the SVGRenderingObserver objects to coalesce invalidations may work + * better? + * + * InvalidateAll must be called before this object is destroyed, i.e. + * before the referenced frame is destroyed. This should normally happen + * via SVGContainerFrame::RemoveFrame, since only frames in the frame + * tree should be referenced. + */ +class SVGRenderingObserverSet { + public: + SVGRenderingObserverSet() : mObservers(4) { + MOZ_COUNT_CTOR(SVGRenderingObserverSet); + } + + ~SVGRenderingObserverSet() { MOZ_COUNT_DTOR(SVGRenderingObserverSet); } + + void Add(SVGRenderingObserver* aObserver) { mObservers.Insert(aObserver); } + void Remove(SVGRenderingObserver* aObserver) { mObservers.Remove(aObserver); } +#ifdef DEBUG + bool Contains(SVGRenderingObserver* aObserver) { + return mObservers.Contains(aObserver); + } +#endif + bool IsEmpty() { return mObservers.IsEmpty(); } + + /** + * Drop all our observers, and notify them that we have changed and dropped + * our reference to them. + */ + void InvalidateAll(); + + /** + * Drop all observers that observe reflow, and notify them that we have + * changed and dropped our reference to them. + */ + void InvalidateAllForReflow(); + + /** + * Drop all our observers, and notify them that we have dropped our reference + * to them. + */ + void RemoveAll(); + + private: + nsTHashSet<SVGRenderingObserver*> mObservers; +}; + +void SVGRenderingObserverSet::InvalidateAll() { + if (mObservers.IsEmpty()) { + return; + } + + const auto observers = std::move(mObservers); + + // We've moved all the observers from mObservers, effectively + // evicting them so we need to notify all observers of eviction + // before we process any rendering changes. In short, don't + // try to merge these loops. + for (const auto& observer : observers) { + observer->NotifyEvictedFromRenderingObserverSet(); + } + for (const auto& observer : observers) { + observer->OnNonDOMMutationRenderingChange(); + } +} + +void SVGRenderingObserverSet::InvalidateAllForReflow() { + if (mObservers.IsEmpty()) { + return; + } + + AutoTArray<SVGRenderingObserver*, 10> observers; + + for (auto it = mObservers.cbegin(), end = mObservers.cend(); it != end; + ++it) { + SVGRenderingObserver* obs = *it; + if (obs->ObservesReflow()) { + observers.AppendElement(obs); + mObservers.Remove(it); + obs->NotifyEvictedFromRenderingObserverSet(); + } + } + + for (const auto& observer : observers) { + observer->OnNonDOMMutationRenderingChange(); + } +} + +void SVGRenderingObserverSet::RemoveAll() { + const auto observers = std::move(mObservers); + + // Our list is now cleared. We need to notify the observers we've removed, + // so they can update their state & remove themselves as mutation-observers. + for (const auto& observer : observers) { + observer->NotifyEvictedFromRenderingObserverSet(); + } +} + +static SVGRenderingObserverSet* GetObserverSet(Element* aElement) { + return static_cast<SVGRenderingObserverSet*>( + aElement->GetProperty(nsGkAtoms::renderingobserverset)); +} + +#ifdef DEBUG +// Defined down here because we need SVGRenderingObserverSet's definition. +void SVGRenderingObserver::DebugObserverSet() { + Element* referencedElement = GetReferencedElementWithoutObserving(); + if (referencedElement) { + SVGRenderingObserverSet* observers = GetObserverSet(referencedElement); + bool inObserverSet = observers && observers->Contains(this); + MOZ_ASSERT(inObserverSet == mInObserverSet, + "failed to track whether we're in our referenced element's " + "observer set!"); + } else { + MOZ_ASSERT(!mInObserverSet, "In whose observer set are we, then?"); + } +} +#endif + +using URIObserverHashtable = + nsInterfaceHashtable<URLAndReferrerInfoHashKey, nsIMutationObserver>; + +using PaintingPropertyDescriptor = + const FramePropertyDescriptor<SVGPaintingProperty>*; + +static void DestroyFilterProperty(SVGFilterObserverListForCSSProp* aProp) { + aProp->Release(); +} + +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefToTemplateProperty, + SVGTemplateElementObserver) +NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BackdropFilterProperty, + SVGFilterObserverListForCSSProp, + DestroyFilterProperty) +NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty, + SVGFilterObserverListForCSSProp, + DestroyFilterProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, SVGMaskObserverList) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, SVGPaintingProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerStartProperty, SVGMarkerObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMidProperty, SVGMarkerObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, SVGMarkerObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, SVGPaintingProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, SVGPaintingProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty, + SVGTextPathObserver) +NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty, + URIObserverHashtable) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(BackgroundClipObserverProperty, + BackgroundClipRenderingObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(OffsetPathProperty, + SVGRenderingObserverProperty) + +template <class T> +static T* GetEffectProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + const FramePropertyDescriptor<T>* aProperty) { + if (!aURI) { + return nullptr; + } + + bool found; + T* prop = aFrame->GetProperty(aProperty, &found); + if (found) { + MOZ_ASSERT(prop, "this property should only store non-null values"); + return prop; + } + prop = new T(aURI, aFrame, false); + NS_ADDREF(prop); + aFrame->AddProperty(aProperty, prop); + return prop; +} + +static SVGPaintingProperty* GetPaintingProperty( + URLAndReferrerInfo* aURI, nsIFrame* aFrame, + const FramePropertyDescriptor<SVGPaintingProperty>* aProperty) { + return GetEffectProperty(aURI, aFrame, aProperty); +} + +static already_AddRefed<URLAndReferrerInfo> GetMarkerURI( + nsIFrame* aFrame, const StyleUrlOrNone nsStyleSVG::*aMarker) { + const StyleUrlOrNone& url = aFrame->StyleSVG()->*aMarker; + if (url.IsNone()) { + return nullptr; + } + return ResolveURLUsingLocalRef(aFrame, url.AsUrl()); +} + +bool SVGObserverUtils::GetAndObserveMarkers(nsIFrame* aMarkedFrame, + SVGMarkerFrame* (*aFrames)[3]) { + MOZ_ASSERT(!aMarkedFrame->GetPrevContinuation() && + aMarkedFrame->IsSVGGeometryFrame() && + static_cast<SVGGeometryElement*>(aMarkedFrame->GetContent()) + ->IsMarkable(), + "Bad frame"); + + bool foundMarker = false; + RefPtr<URLAndReferrerInfo> markerURL; + SVGMarkerObserver* observer; + nsIFrame* marker; + +#define GET_MARKER(type) \ + markerURL = GetMarkerURI(aMarkedFrame, &nsStyleSVG::mMarker##type); \ + observer = \ + GetEffectProperty(markerURL, aMarkedFrame, Marker##type##Property()); \ + marker = observer ? observer->GetAndObserveReferencedFrame( \ + LayoutFrameType::SVGMarker, nullptr) \ + : nullptr; \ + foundMarker = foundMarker || bool(marker); \ + (*aFrames)[SVGMark::e##type] = static_cast<SVGMarkerFrame*>(marker); + + GET_MARKER(Start) + GET_MARKER(Mid) + GET_MARKER(End) + +#undef GET_MARKER + + return foundMarker; +} + +// Note that the returned list will be empty in the case of a 'filter' property +// that only specifies CSS filter functions (no url()'s to SVG filters). +template <typename P> +static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS( + nsIFrame* aFrame, bool aHasFilters, + FrameProperties::Descriptor<P> aProperty, + Span<const StyleFilter> aFilters) { + if (!aHasFilters) { + return nullptr; + } + + bool found; + SVGFilterObserverListForCSSProp* observers = + aFrame->GetProperty(aProperty, &found); + if (found) { + MOZ_ASSERT(observers, "this property should only store non-null values"); + return observers; + } + observers = new SVGFilterObserverListForCSSProp(aFilters, aFrame); + NS_ADDREF(observers); + aFrame->AddProperty(aProperty, observers); + return observers; +} + +static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS( + nsIFrame* aFrame, StyleFilterType aStyleFilterType) { + MOZ_ASSERT(!aFrame->GetPrevContinuation(), "Require first continuation"); + + const nsStyleEffects* effects = aFrame->StyleEffects(); + + return aStyleFilterType == StyleFilterType::BackdropFilter + ? GetOrCreateFilterObserverListForCSS( + aFrame, effects->HasBackdropFilters(), + BackdropFilterProperty(), effects->mBackdropFilters.AsSpan()) + : GetOrCreateFilterObserverListForCSS( + aFrame, effects->HasFilters(), FilterProperty(), + effects->mFilters.AsSpan()); +} + +static SVGObserverUtils::ReferenceState GetAndObserveFilters( + SVGFilterObserverList* aObserverList, + nsTArray<SVGFilterFrame*>* aFilterFrames) { + if (!aObserverList) { + return SVGObserverUtils::eHasNoRefs; + } + + const nsTArray<RefPtr<SVGFilterObserver>>& observers = + aObserverList->GetObservers(); + if (observers.IsEmpty()) { + return SVGObserverUtils::eHasNoRefs; + } + + for (const auto& observer : observers) { + SVGFilterFrame* filter = observer->GetAndObserveFilterFrame(); + if (!filter) { + if (aFilterFrames) { + aFilterFrames->Clear(); + } + return SVGObserverUtils::eHasRefsSomeInvalid; + } + if (aFilterFrames) { + aFilterFrames->AppendElement(filter); + } + } + + return SVGObserverUtils::eHasRefsAllValid; +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters( + nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames, + StyleFilterType aStyleFilterType) { + SVGFilterObserverListForCSSProp* observerList = + GetOrCreateFilterObserverListForCSS(aFilteredFrame, aStyleFilterType); + return mozilla::GetAndObserveFilters(observerList, aFilterFrames); +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters( + nsISupports* aObserverList, nsTArray<SVGFilterFrame*>* aFilterFrames) { + return mozilla::GetAndObserveFilters( + static_cast<SVGFilterObserverListForCanvasContext*>(aObserverList), + aFilterFrames); +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetFiltersIfObserving( + nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames) { + SVGFilterObserverListForCSSProp* observerList = + aFilteredFrame->GetProperty(FilterProperty()); + return mozilla::GetAndObserveFilters(observerList, aFilterFrames); +} + +already_AddRefed<nsISupports> SVGObserverUtils::ObserveFiltersForCanvasContext( + CanvasRenderingContext2D* aContext, Element* aCanvasElement, + const Span<const StyleFilter> aFilters) { + return do_AddRef(new SVGFilterObserverListForCanvasContext( + aContext, aCanvasElement, aFilters)); +} + +void SVGObserverUtils::DetachFromCanvasContext(nsISupports* aAutoObserver) { + static_cast<SVGFilterObserverListForCanvasContext*>(aAutoObserver) + ->DetachFromContext(); +} + +static SVGPaintingProperty* GetOrCreateClipPathObserver( + nsIFrame* aClippedFrame) { + MOZ_ASSERT(!aClippedFrame->GetPrevContinuation(), + "Require first continuation"); + + const nsStyleSVGReset* svgStyleReset = aClippedFrame->StyleSVGReset(); + if (!svgStyleReset->mClipPath.IsUrl()) { + return nullptr; + } + const auto& url = svgStyleReset->mClipPath.AsUrl(); + RefPtr<URLAndReferrerInfo> pathURI = + ResolveURLUsingLocalRef(aClippedFrame, url); + return GetPaintingProperty(pathURI, aClippedFrame, ClipPathProperty()); +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveClipPath( + nsIFrame* aClippedFrame, SVGClipPathFrame** aClipPathFrame) { + if (aClipPathFrame) { + *aClipPathFrame = nullptr; + } + SVGPaintingProperty* observers = GetOrCreateClipPathObserver(aClippedFrame); + if (!observers) { + return eHasNoRefs; + } + bool frameTypeOK = true; + SVGClipPathFrame* frame = + static_cast<SVGClipPathFrame*>(observers->GetAndObserveReferencedFrame( + LayoutFrameType::SVGClipPath, &frameTypeOK)); + // Note that, unlike for filters, a reference to an ID that doesn't exist + // is not invalid for clip-path or mask. + if (!frameTypeOK) { + return eHasRefsSomeInvalid; + } + if (aClipPathFrame) { + *aClipPathFrame = frame; + } + return frame ? eHasRefsAllValid : eHasNoRefs; +} + +static SVGRenderingObserverProperty* GetOrCreateGeometryObserver( + nsIFrame* aFrame) { + // Now only offset-path property uses this. See MotionPathUtils.cpp. + const nsStyleDisplay* disp = aFrame->StyleDisplay(); + if (!disp->mOffsetPath.IsUrl()) { + return nullptr; + } + const auto& url = disp->mOffsetPath.AsUrl(); + RefPtr<URLAndReferrerInfo> pathURI = ResolveURLUsingLocalRef(aFrame, url); + return GetEffectProperty(pathURI, aFrame, OffsetPathProperty()); +} + +SVGGeometryElement* SVGObserverUtils::GetAndObserveGeometry(nsIFrame* aFrame) { + SVGRenderingObserverProperty* observers = GetOrCreateGeometryObserver(aFrame); + if (!observers) { + return nullptr; + } + + bool frameTypeOK = true; + SVGGeometryFrame* frame = + do_QueryFrame(observers->GetAndObserveReferencedFrame( + LayoutFrameType::SVGGeometry, &frameTypeOK)); + if (!frameTypeOK || !frame) { + return nullptr; + } + + return static_cast<dom::SVGGeometryElement*>(frame->GetContent()); +} + +static SVGMaskObserverList* GetOrCreateMaskObserverList( + nsIFrame* aMaskedFrame) { + MOZ_ASSERT(!aMaskedFrame->GetPrevContinuation(), + "Require first continuation"); + + const nsStyleSVGReset* style = aMaskedFrame->StyleSVGReset(); + if (!style->HasMask()) { + return nullptr; + } + + MOZ_ASSERT(style->mMask.mImageCount > 0); + + bool found; + SVGMaskObserverList* prop = aMaskedFrame->GetProperty(MaskProperty(), &found); + if (found) { + MOZ_ASSERT(prop, "this property should only store non-null values"); + return prop; + } + prop = new SVGMaskObserverList(aMaskedFrame); + NS_ADDREF(prop); + aMaskedFrame->AddProperty(MaskProperty(), prop); + return prop; +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveMasks( + nsIFrame* aMaskedFrame, nsTArray<SVGMaskFrame*>* aMaskFrames) { + SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame); + if (!observerList) { + return eHasNoRefs; + } + + const nsTArray<RefPtr<SVGPaintingProperty>>& observers = + observerList->GetObservers(); + if (observers.IsEmpty()) { + return eHasNoRefs; + } + + ReferenceState state = eHasRefsAllValid; + + for (size_t i = 0; i < observers.Length(); i++) { + bool frameTypeOK = true; + SVGMaskFrame* maskFrame = + static_cast<SVGMaskFrame*>(observers[i]->GetAndObserveReferencedFrame( + LayoutFrameType::SVGMask, &frameTypeOK)); + MOZ_ASSERT(!maskFrame || frameTypeOK); + // XXXjwatt: this looks fishy + if (!frameTypeOK) { + // We can not find the specific SVG mask resource in the downloaded SVG + // document. There are two possibilities: + // 1. The given resource id is invalid. + // 2. The given resource id refers to a viewbox. + // + // Hand it over to the style image. + observerList->ResolveImage(i); + state = eHasRefsSomeInvalid; + } + if (aMaskFrames) { + aMaskFrames->AppendElement(maskFrame); + } + } + + return state; +} + +SVGGeometryElement* SVGObserverUtils::GetAndObserveTextPathsPath( + nsIFrame* aTextPathFrame) { + // Continuations can come and go during reflow, and we don't need to observe + // the referenced element more than once for a given node. + aTextPathFrame = aTextPathFrame->FirstContinuation(); + + SVGTextPathObserver* property = + aTextPathFrame->GetProperty(HrefAsTextPathProperty()); + + if (!property) { + nsIContent* content = aTextPathFrame->GetContent(); + nsAutoString href; + static_cast<SVGTextPathElement*>(content)->HrefAsString(href); + if (href.IsEmpty()) { + return nullptr; // no URL + } + + RefPtr<URLAndReferrerInfo> target = ResolveURLUsingLocalRef(content, href); + + property = + GetEffectProperty(target, aTextPathFrame, HrefAsTextPathProperty()); + if (!property) { + return nullptr; + } + } + + return SVGGeometryElement::FromNodeOrNull( + property->GetAndObserveReferencedElement()); +} + +SVGGeometryElement* SVGObserverUtils::GetAndObserveMPathsPath( + SVGMPathElement* aSVGMPathElement) { + if (!aSVGMPathElement->mMPathObserver) { + nsAutoString href; + aSVGMPathElement->HrefAsString(href); + if (href.IsEmpty()) { + return nullptr; // no URL + } + + RefPtr<URLAndReferrerInfo> target = + ResolveURLUsingLocalRef(aSVGMPathElement, href); + + aSVGMPathElement->mMPathObserver = + new SVGMPathObserver(target, aSVGMPathElement); + } + + return SVGGeometryElement::FromNodeOrNull( + static_cast<SVGMPathObserver*>(aSVGMPathElement->mMPathObserver.get()) + ->GetAndObserveReferencedElement()); +} + +void SVGObserverUtils::TraverseMPathObserver( + SVGMPathElement* aSVGMPathElement, + nsCycleCollectionTraversalCallback* aCB) { + if (aSVGMPathElement->mMPathObserver) { + static_cast<SVGMPathObserver*>(aSVGMPathElement->mMPathObserver.get()) + ->Traverse(aCB); + } +} + +void SVGObserverUtils::InitiateResourceDocLoads(nsIFrame* aFrame) { + // We create observer objects and attach them to aFrame, but we do not + // make aFrame start observing the referenced frames. + Unused << GetOrCreateFilterObserverListForCSS( + aFrame, StyleFilterType::BackdropFilter); + Unused << GetOrCreateFilterObserverListForCSS(aFrame, + StyleFilterType::Filter); + Unused << GetOrCreateClipPathObserver(aFrame); + Unused << GetOrCreateGeometryObserver(aFrame); + Unused << GetOrCreateMaskObserverList(aFrame); +} + +void SVGObserverUtils::RemoveTextPathObserver(nsIFrame* aTextPathFrame) { + aTextPathFrame->RemoveProperty(HrefAsTextPathProperty()); +} + +nsIFrame* SVGObserverUtils::GetAndObserveTemplate( + nsIFrame* aFrame, HrefToTemplateCallback aGetHref) { + SVGTemplateElementObserver* observer = + aFrame->GetProperty(HrefToTemplateProperty()); + + if (!observer) { + nsAutoString href; + aGetHref(href); + if (href.IsEmpty()) { + return nullptr; // no URL + } + + RefPtr<URLAndReferrerInfo> info = + ResolveURLUsingLocalRef(aFrame->GetContent(), href); + + observer = GetEffectProperty(info, aFrame, HrefToTemplateProperty()); + } + + return observer ? observer->GetAndObserveReferencedFrame() : nullptr; +} + +void SVGObserverUtils::RemoveTemplateObserver(nsIFrame* aFrame) { + aFrame->RemoveProperty(HrefToTemplateProperty()); +} + +Element* SVGObserverUtils::GetAndObserveBackgroundImage(nsIFrame* aFrame, + const nsAtom* aHref) { + bool found; + URIObserverHashtable* hashtable = + aFrame->GetProperty(BackgroundImageProperty(), &found); + if (!found) { + hashtable = new URIObserverHashtable(); + aFrame->AddProperty(BackgroundImageProperty(), hashtable); + } else { + MOZ_ASSERT(hashtable, "this property should only store non-null values"); + } + + nsAutoString elementId = u"#"_ns + nsDependentAtomString(aHref); + nsCOMPtr<nsIURI> targetURI; + nsContentUtils::NewURIWithDocumentCharset( + getter_AddRefs(targetURI), elementId, + aFrame->GetContent()->GetUncomposedDoc(), + aFrame->GetContent()->GetBaseURI()); + nsIReferrerInfo* referrerInfo = + aFrame->GetContent() + ->OwnerDoc() + ->ReferrerInfoForInternalCSSAndSVGResources(); + RefPtr<URLAndReferrerInfo> url = + new URLAndReferrerInfo(targetURI, referrerInfo); + + return static_cast<SVGMozElementObserver*>( + hashtable + ->LookupOrInsertWith( + url, + [&] { + return MakeRefPtr<SVGMozElementObserver>(url, aFrame); + }) + .get()) + ->GetAndObserveReferencedElement(); +} + +Element* SVGObserverUtils::GetAndObserveBackgroundClip(nsIFrame* aFrame) { + bool found; + BackgroundClipRenderingObserver* obs = + aFrame->GetProperty(BackgroundClipObserverProperty(), &found); + if (!found) { + obs = new BackgroundClipRenderingObserver(aFrame); + NS_ADDREF(obs); + aFrame->AddProperty(BackgroundClipObserverProperty(), obs); + } + + return obs->GetAndObserveReferencedElement(); +} + +SVGPaintServerFrame* SVGObserverUtils::GetAndObservePaintServer( + nsIFrame* aPaintedFrame, StyleSVGPaint nsStyleSVG::*aPaint) { + // If we're looking at a frame within SVG text, then we need to look up + // to find the right frame to get the painting property off. We should at + // least look up past a text frame, and if the text frame's parent is the + // anonymous block frame, then we look up to its parent (the SVGTextFrame). + nsIFrame* paintedFrame = aPaintedFrame; + if (paintedFrame->IsInSVGTextSubtree()) { + paintedFrame = paintedFrame->GetParent(); + nsIFrame* grandparent = paintedFrame->GetParent(); + if (grandparent && grandparent->IsSVGTextFrame()) { + paintedFrame = grandparent; + } + } + + const nsStyleSVG* svgStyle = paintedFrame->StyleSVG(); + if (!(svgStyle->*aPaint).kind.IsPaintServer()) { + return nullptr; + } + + RefPtr<URLAndReferrerInfo> paintServerURL = ResolveURLUsingLocalRef( + paintedFrame, (svgStyle->*aPaint).kind.AsPaintServer()); + + MOZ_ASSERT(aPaint == &nsStyleSVG::mFill || aPaint == &nsStyleSVG::mStroke); + PaintingPropertyDescriptor propDesc = + (aPaint == &nsStyleSVG::mFill) ? FillProperty() : StrokeProperty(); + if (auto* property = + GetPaintingProperty(paintServerURL, paintedFrame, propDesc)) { + return do_QueryFrame(property->GetAndObserveReferencedFrame()); + } + return nullptr; +} + +void SVGObserverUtils::UpdateEffects(nsIFrame* aFrame) { + NS_ASSERTION(aFrame->GetContent()->IsElement(), + "aFrame's content should be an element"); + + aFrame->RemoveProperty(BackdropFilterProperty()); + aFrame->RemoveProperty(FilterProperty()); + aFrame->RemoveProperty(MaskProperty()); + aFrame->RemoveProperty(ClipPathProperty()); + aFrame->RemoveProperty(MarkerStartProperty()); + aFrame->RemoveProperty(MarkerMidProperty()); + aFrame->RemoveProperty(MarkerEndProperty()); + aFrame->RemoveProperty(FillProperty()); + aFrame->RemoveProperty(StrokeProperty()); + aFrame->RemoveProperty(BackgroundImageProperty()); + + // Ensure that the filter is repainted correctly + // We can't do that in OnRenderingChange as the referenced frame may + // not be valid + GetOrCreateFilterObserverListForCSS(aFrame, StyleFilterType::BackdropFilter); + GetOrCreateFilterObserverListForCSS(aFrame, StyleFilterType::Filter); + + if (aFrame->IsSVGGeometryFrame() && + static_cast<SVGGeometryElement*>(aFrame->GetContent())->IsMarkable()) { + // Set marker properties here to avoid reference loops + RefPtr<URLAndReferrerInfo> markerURL = + GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart); + GetEffectProperty(markerURL, aFrame, MarkerStartProperty()); + markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid); + GetEffectProperty(markerURL, aFrame, MarkerMidProperty()); + markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd); + GetEffectProperty(markerURL, aFrame, MarkerEndProperty()); + } +} + +void SVGObserverUtils::AddRenderingObserver(Element* aElement, + SVGRenderingObserver* aObserver) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (!observers) { + observers = new SVGRenderingObserverSet(); + // When we call cloneAndAdopt we keep the property. If the referenced + // element doesn't exist in the new document then the observer set and + // observers will be removed by ElementTracker::ElementChanged when we + // get the ChangeNotification. + aElement->SetProperty(nsGkAtoms::renderingobserverset, observers, + nsINode::DeleteProperty<SVGRenderingObserverSet>, + /* aTransfer = */ true); + } + aElement->SetHasRenderingObservers(true); + observers->Add(aObserver); +} + +void SVGObserverUtils::RemoveRenderingObserver( + Element* aElement, SVGRenderingObserver* aObserver) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (observers) { + NS_ASSERTION(observers->Contains(aObserver), + "removing observer from an element we're not observing?"); + observers->Remove(aObserver); + if (observers->IsEmpty()) { + aElement->RemoveProperty(nsGkAtoms::renderingobserverset); + aElement->SetHasRenderingObservers(false); + } + } +} + +void SVGObserverUtils::RemoveAllRenderingObservers(Element* aElement) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (observers) { + observers->RemoveAll(); + aElement->RemoveProperty(nsGkAtoms::renderingobserverset); + aElement->SetHasRenderingObservers(false); + } +} + +void SVGObserverUtils::InvalidateRenderingObservers(nsIFrame* aFrame) { + NS_ASSERTION(!aFrame->GetPrevContinuation(), + "aFrame must be first continuation"); + + auto* element = Element::FromNodeOrNull(aFrame->GetContent()); + if (!element) { + return; + } + + // If the rendering has changed, the bounds may well have changed too: + aFrame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty()); + + if (auto* observers = GetObserverSet(element)) { + observers->InvalidateAll(); + return; + } + + if (aFrame->IsRenderingObserverContainer()) { + return; + } + + // Check ancestor SVG containers. The root frame cannot be of type + // eSVGContainer so we don't have to check f for null here. + for (nsIFrame* f = aFrame->GetParent(); f->IsSVGContainerFrame(); + f = f->GetParent()) { + if (auto* element = Element::FromNode(f->GetContent())) { + if (auto* observers = GetObserverSet(element)) { + observers->InvalidateAll(); + return; + } + } + if (f->IsRenderingObserverContainer()) { + return; + } + } +} + +void SVGObserverUtils::InvalidateDirectRenderingObservers( + Element* aElement, uint32_t aFlags /* = 0 */) { + if (nsIFrame* frame = aElement->GetPrimaryFrame()) { + // If the rendering has changed, the bounds may well have changed too: + frame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty()); + } + + if (aElement->HasRenderingObservers()) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (observers) { + if (aFlags & INVALIDATE_REFLOW) { + observers->InvalidateAllForReflow(); + } else { + observers->InvalidateAll(); + } + } + } +} + +void SVGObserverUtils::InvalidateDirectRenderingObservers( + nsIFrame* aFrame, uint32_t aFlags /* = 0 */) { + if (auto* element = Element::FromNodeOrNull(aFrame->GetContent())) { + InvalidateDirectRenderingObservers(element, aFlags); + } +} + +} // namespace mozilla |