/* -*- 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(aKey->GetReferrerInfo())->Hash()); } enum { ALLOW_MEMMOVE = true }; protected: RefPtr mKey; }; /** * Return a baseURL for resolving a local-ref URL. * * @param aContent an element which uses a local-ref property. Here are some * examples: * * * */ static already_AddRefed 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 , 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 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 ResolveURLUsingLocalRef( nsIFrame* aFrame, const StyleComputedImageUrl& aURL) { MOZ_ASSERT(aFrame); nsCOMPtr 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 ResolveURLUsingLocalRef( nsIContent* aContent, const nsAString& aURL) { // Like GetBaseURLForLocalRef, we want to resolve the // URL against any 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 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 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 element"); auto* text = static_cast( 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(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( 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 aFilters, nsIContent* aFilteredElement, nsIFrame* aFilteredFrame = nullptr); const nsTArray>& 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> 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 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 filterURL; if (aFilteredFrame) { filterURL = ResolveURLUsingLocalRef(aFilteredFrame, url); } else { nsCOMPtr resolvedURI = url.ResolveLocalRef(aFilteredElement); if (resolvedURI) { filterURL = new URLAndReferrerInfo(resolvedURI, url.ExtraData()); } } RefPtr observer = new SVGFilterObserver(filterURL, aFilteredElement, this); mObservers.AppendElement(observer); } } SVGFilterObserverList::~SVGFilterObserverList() { DetachObservers(); } class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList { public: SVGFilterObserverListForCSSProp(Span aFilters, nsIFrame* aFilteredFrame) : SVGFilterObserverList(aFilters, aFilteredFrame->GetContent(), aFilteredFrame) {} protected: void OnRenderingChange() override; bool mInvalidating = false; }; void SVGFilterObserverListForCSSProp::OnRenderingChange() { if (mInvalidating) { return; } AutoRestore guard(mInvalidating); mInvalidating = true; for (auto& observer : mObservers) { observer->OnRenderingChange(); } } class SVGFilterObserverListForCanvasContext final : public SVGFilterObserverList { public: SVGFilterObserverListForCanvasContext(CanvasRenderingContext2D* aContext, Element* aCanvasElement, Span 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 kungFuDeathGrip(mContext); kungFuDeathGrip->UpdateFilter(/* aFlushIfNeeded = */ false); } class SVGMaskObserverList final : public nsISupports { public: explicit SVGMaskObserverList(nsIFrame* aFrame); // nsISupports NS_DECL_ISUPPORTS const nsTArray>& GetObservers() const { return mProperties; } void ResolveImage(uint32_t aIndex); private: virtual ~SVGMaskObserverList() = default; // non-public nsTArray> 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 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(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 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 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( 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; using PaintingPropertyDescriptor = const FramePropertyDescriptor*; 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 static T* GetEffectProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame, const FramePropertyDescriptor* 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* aProperty) { return GetEffectProperty(aURI, aFrame, aProperty); } static already_AddRefed 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(aMarkedFrame->GetContent()) ->IsMarkable(), "Bad frame"); bool foundMarker = false; RefPtr 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(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 static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS( nsIFrame* aFrame, bool aHasFilters, FrameProperties::Descriptor

aProperty, Span 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* aFilterFrames) { if (!aObserverList) { return SVGObserverUtils::eHasNoRefs; } const nsTArray>& 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* aFilterFrames, StyleFilterType aStyleFilterType) { SVGFilterObserverListForCSSProp* observerList = GetOrCreateFilterObserverListForCSS(aFilteredFrame, aStyleFilterType); return mozilla::GetAndObserveFilters(observerList, aFilterFrames); } SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters( nsISupports* aObserverList, nsTArray* aFilterFrames) { return mozilla::GetAndObserveFilters( static_cast(aObserverList), aFilterFrames); } SVGObserverUtils::ReferenceState SVGObserverUtils::GetFiltersIfObserving( nsIFrame* aFilteredFrame, nsTArray* aFilterFrames) { SVGFilterObserverListForCSSProp* observerList = aFilteredFrame->GetProperty(FilterProperty()); return mozilla::GetAndObserveFilters(observerList, aFilterFrames); } already_AddRefed SVGObserverUtils::ObserveFiltersForCanvasContext( CanvasRenderingContext2D* aContext, Element* aCanvasElement, const Span aFilters) { return do_AddRef(new SVGFilterObserverListForCanvasContext( aContext, aCanvasElement, aFilters)); } void SVGObserverUtils::DetachFromCanvasContext(nsISupports* aAutoObserver) { static_cast(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 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(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 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(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* aMaskFrames) { SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame); if (!observerList) { return eHasNoRefs; } const nsTArray>& 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(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(content)->HrefAsString(href); if (href.IsEmpty()) { return nullptr; // no URL } RefPtr 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 target = ResolveURLUsingLocalRef(aSVGMPathElement, href); aSVGMPathElement->mMPathObserver = new SVGMPathObserver(target, aSVGMPathElement); } return SVGGeometryElement::FromNodeOrNull( static_cast(aSVGMPathElement->mMPathObserver.get()) ->GetAndObserveReferencedElement()); } void SVGObserverUtils::TraverseMPathObserver( SVGMPathElement* aSVGMPathElement, nsCycleCollectionTraversalCallback* aCB) { if (aSVGMPathElement->mMPathObserver) { static_cast(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 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 targetURI; nsContentUtils::NewURIWithDocumentCharset( getter_AddRefs(targetURI), elementId, aFrame->GetContent()->GetUncomposedDoc(), aFrame->GetContent()->GetBaseURI()); nsIReferrerInfo* referrerInfo = aFrame->GetContent() ->OwnerDoc() ->ReferrerInfoForInternalCSSAndSVGResources(); RefPtr url = new URLAndReferrerInfo(targetURI, referrerInfo); return static_cast( hashtable ->LookupOrInsertWith( url, [&] { return MakeRefPtr(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 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(aFrame->GetContent())->IsMarkable()) { // Set marker properties here to avoid reference loops RefPtr 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, /* 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